OSDN Git Service

AppConfig設定ダイアログでの編集方法を文字列ではなくデータ型で編集できるように刷新した。
authorseraphy <seraphy@users.osdn.me>
Tue, 11 Dec 2018 14:55:44 +0000 (23:55 +0900)
committerseraphy <seraphy@users.osdn.me>
Tue, 11 Dec 2018 14:55:44 +0000 (23:55 +0900)
src/main/java/charactermanaj/model/AppConfig.java
src/main/java/charactermanaj/ui/AppConfigDialog.java
src/main/java/charactermanaj/ui/PreviewPanel.java
src/main/java/charactermanaj/util/BeanPropertiesUtilities.java
src/main/resources/languages/appconfigdialog.xml
src/main/resources/languages/appconfigdialog_ja.xml
src/main/resources/languages/appconfigdialog_zh.xml

index 2499b05..019095f 100644 (file)
@@ -14,8 +14,10 @@ import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.logging.Level;
@@ -24,8 +26,8 @@ import java.util.regex.Pattern;
 
 import charactermanaj.util.ApplicationLogHandler;
 import charactermanaj.util.BeanPropertiesUtilities;
-import charactermanaj.util.BeanPropertiesUtilities.StringConverterSpec;
-import charactermanaj.util.BeanPropertiesUtilities.UnsignedHexStringConverter;
+import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessor;
+import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessorMap;
 import charactermanaj.util.ConfigurationDirUtilities;
 
 /**
@@ -378,14 +380,15 @@ public final class AppConfig {
         *            適用するプロパティ
         * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
         */
-       public static Set<String> checkProperties(Properties props) {
+       public static Set<String> checkProperties(Map<String, Object> props) {
                if (props == null) {
                        throw new IllegalArgumentException();
                }
                AppConfig dummy = new AppConfig(); // アプリケーションから参照されないダミーのインスタンスを作成する.
-               return BeanPropertiesUtilities.loadFromProperties(dummy, props);
+               return update(props, dummy);
        }
 
+
        /**
         * Propertiesの値で設定を更新する.<br>
         *
@@ -393,11 +396,57 @@ public final class AppConfig {
         *            適用するプロパティ
         * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
         */
-       public Set<String> update(Properties props) {
+       public Set<String> update(Map<String, Object> props) {
+               return update(props, this);
+       }
+
+       /**
+        * Propertiesの値で設定を更新する.<br>
+        *
+        * @param props
+        *            適用するプロパティ
+        * @param bean
+        *            適用するAppConfigのインスタンス(ドライランと本番の切り替え用)
+        * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
+        */
+       private static Set<String> update(Map<String, Object>  props, AppConfig bean) {
                if (props == null) {
                        throw new IllegalArgumentException();
                }
-               return BeanPropertiesUtilities.loadFromProperties(this, props);
+               HashSet<String> rejectedNames = new HashSet<String>();
+
+               PropertyAccessorMap accessorMap = BeanPropertiesUtilities.getPropertyAccessorMap(AppConfig.class);
+               accessorMap.setBean(bean);
+
+               for (Map.Entry<String, Object> propEntry : props.entrySet()) {
+                       String name = propEntry.getKey();
+                       Object value = propEntry.getValue();
+
+                       PropertyAccessor accessor = accessorMap.get(name);
+                       if (accessor == null) {
+                               // プロパティがない
+                               rejectedNames.add(name);
+                               continue;
+                       }
+
+                       Class<?> propertyType = accessor.getPropertyType();
+                       if (propertyType.isPrimitive() && value == null) {
+                               // プリミティブ型なのにnullは入れられない
+                               rejectedNames.add(name);
+                               continue;
+                       }
+
+                       try {
+                               accessor.setValue(value);
+
+                       } catch (Exception ex) {
+                               // 何らかの理由でプロパティの設定に失敗している場合
+                               rejectedNames.add(name);
+                               logger.log(Level.WARNING, "invalid propery: " + name + " /val=" + value, ex);
+                               continue;
+                       }
+               }
+               return rejectedNames;
        }
 
        /**
@@ -1203,18 +1252,17 @@ public final class AppConfig {
                }
        }
 
-       private int previewGridColor = 0x7f7f0000;
+       private Color previewGridColor = new Color(0x7f7f0000, true);
 
-       @StringConverterSpec(UnsignedHexStringConverter.class)
-       public int getPreviewGridColor() {
+       public Color getPreviewGridColor() {
                return previewGridColor;
        }
 
        public static final String PREVIEW_GRID_COLOR = "previewGridColor";
 
-       public void setPreviewGridColor(int previewGridColor) {
-               int old = this.previewGridColor;
-               if (old != previewGridColor) {
+       public void setPreviewGridColor(Color previewGridColor) {
+               Color old = this.previewGridColor;
+               if (old == null ? previewGridColor != null : !old.equals(previewGridColor)) {
                        this.previewGridColor = previewGridColor;
                        propChangeSupport.firePropertyChange(PREVIEW_GRID_COLOR, old, previewGridColor);
                }
index 6acb5f1..a03d834 100644 (file)
@@ -4,24 +4,32 @@ import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.Container;
+import java.awt.Dimension;
 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.ActionListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.swing.AbstractAction;
+import javax.swing.AbstractCellEditor;
 import javax.swing.Action;
 import javax.swing.ActionMap;
 import javax.swing.BorderFactory;
@@ -29,6 +37,7 @@ import javax.swing.Box;
 import javax.swing.InputMap;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
+import javax.swing.JColorChooser;
 import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JFrame;
@@ -39,12 +48,20 @@ import javax.swing.JRootPane;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.KeyStroke;
+import javax.swing.SwingConstants;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumnModel;
 
 import charactermanaj.Main;
 import charactermanaj.model.AppConfig;
-import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
+import charactermanaj.util.BeanPropertiesUtilities;
+import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessor;
+import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessorMap;
 import charactermanaj.util.ConfigurationDirUtilities;
 import charactermanaj.util.DesktopUtilities;
 import charactermanaj.util.ErrorMessageHelper;
@@ -54,23 +71,256 @@ import charactermanaj.util.SetupLocalization;
 
 /**
  * アプリケーション設定ダイアログ
- * 
+ *
  * @author seraphy
  */
 public class AppConfigDialog extends JDialog {
 
        private static final long serialVersionUID = 1L;
-       
+
        private static final Logger logger = Logger.getLogger(AppConfigDialog.class.getName());
 
        private AppConfigTableModel appConfigTableModel;
-       
+
        private JTable appConfigTable;
-       
+
        private JCheckBox chkResetDoNotAskAgain;
-       
+
        private RecentCharactersDir recentCharactersDir;
-       
+
+       private AbstractAction actApply;
+
+       private boolean orgDoNotAskAgain;
+
+       public enum ColumnDef {
+               NAME("column.key", String.class, false) {
+                       @Override
+                       public Object getValue(AppConfigRow row) {
+                               return row.getDisplayName();
+                       }
+               },
+               VALUE("column.value", String.class, true) {
+                       @Override
+                       public Object getValue(AppConfigRow row) {
+                               return row.getValue();
+                       }
+                       @Override
+                       public void setValue(AppConfigRow row, Object value) {
+                               row.setValue(value);
+                       }
+               };
+
+               private final String reskey;
+
+               private final Class<?> type;
+
+               private final boolean editable;
+
+               ColumnDef(String reskey, Class<?> type, boolean editable) {
+                       this.reskey = reskey;
+                       this.type = type;
+                       this.editable = editable;
+               }
+
+               public boolean isEditable() {
+                       return editable;
+               }
+
+               public String getResourceKey() {
+                       return reskey;
+               }
+
+               public Class<?> getType() {
+                       return type;
+               }
+
+               public abstract Object getValue(AppConfigRow row);
+
+               public void setValue(AppConfigRow row, Object value) {
+                       throw new UnsupportedOperationException(name());
+               }
+       }
+
+       private static class AppConfigRow {
+
+               private final String name;
+
+               private final PropertyAccessor accessor;
+
+               private String order = "";
+
+               private String displayName;
+
+               private Object orgValue;
+
+               private Object value;
+
+               private boolean rejected;
+
+               public AppConfigRow(String name, PropertyAccessor accessor, Object value) {
+                       this.name = name;
+                       this.accessor = accessor;
+                       this.value = value;
+                       this.orgValue = value;
+               }
+
+               public String getName() {
+                       return name;
+               }
+
+               public Class<?> getPropertyType() {
+                       Class<?> dataType = accessor.getPropertyType();
+                       // JTableのセルレンダラーではプリミティブ型の編集は対応していないので
+                       // ラッパー型に置き換える
+                       if (dataType.isPrimitive()) {
+                               if (dataType.equals(int.class)) {
+                                       dataType = Integer.class;
+                               } else if (dataType.equals(long.class)) {
+                                       dataType = Long.class;
+                               } else if (dataType.equals(float.class)) {
+                                       dataType = Float.class;
+                               } else if (dataType.equals(double.class)) {
+                                       dataType = Double.class;
+                               } else if (dataType.equals(boolean.class)) {
+                                       dataType = Boolean.class;
+                               }
+                       }
+                       return dataType;
+               }
+
+               public String getOrder() {
+                       return order;
+               }
+
+               public void setOrder(String order) {
+                       if (order == null) {
+                               order = "";
+                       }
+                       this.order = order;
+               }
+
+               public String getDisplayName() {
+                       return (displayName == null || displayName.length() == 0) ? name : displayName;
+               }
+
+               public void setDisplayName(String displayName) {
+                       this.displayName = displayName;
+               }
+
+               public Object getValue() {
+                       return value;
+               }
+
+               public void setValue(Object value) {
+                       this.value = value;
+               }
+
+               public boolean isRejected() {
+                       return rejected;
+               }
+
+               public void setRejected(boolean rejected) {
+                       this.rejected = rejected;
+               }
+
+               public boolean isModified() {
+                       return orgValue == null ? value != null : !orgValue.equals(value);
+               }
+       }
+
+       private static class AppConfigTableModel extends AbstractTableModel {
+
+               private static final long serialVersionUID = 1L;
+
+               protected static final ColumnDef[] COLUMNS = ColumnDef.values();
+
+               private List<AppConfigRow> items = Collections.emptyList();
+
+               public List<AppConfigRow> getItems() {
+                       return items;
+               }
+
+               public void setItems(List<AppConfigRow> items) {
+                       if (items == null) {
+                               items = Collections.emptyList();
+                       }
+                       this.items = items;
+                       fireTableDataChanged();
+               }
+
+               public void setRejectNames(Set<String> rejectNames) {
+                       if (rejectNames == null) {
+                               rejectNames = Collections.emptySet();
+                       }
+                       for (AppConfigRow item : items) {
+                               String key = item.getName();
+                               boolean rejected = rejectNames.contains(key);
+                               item.setRejected(rejected);
+                       }
+                       fireTableDataChanged();
+               }
+
+               /**
+                * 編集されているか?
+                *
+                * @return 編集されていればtrue、そうでなければfalse
+                */
+               public boolean isModified() {
+                       return false;
+               }
+
+
+               @Override
+               public int getRowCount() {
+                       return items.size();
+               }
+
+               public int getColumnCount() {
+                       return COLUMNS.length;
+               }
+
+               @Override
+               public Class<?> getColumnClass(int columnIndex) {
+                       return COLUMNS[columnIndex].getType();
+               }
+
+               @Override
+               public String getColumnName(int column) {
+                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                                       .getLocalizedProperties("languages/appconfigdialog");
+                       String reskey = COLUMNS[column].getResourceKey();
+                       return strings.getProperty(reskey, reskey);
+               }
+
+               @Override
+               public boolean isCellEditable(int rowIndex, int columnIndex) {
+                       return COLUMNS[columnIndex].isEditable();
+               }
+
+               public Object getValueAt(int rowIndex, int columnIndex) {
+                       AppConfigRow row = items.get(rowIndex);
+                       return COLUMNS[columnIndex].getValue(row);
+               }
+
+               @Override
+               public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+                       AppConfigRow row = items.get(rowIndex);
+                       COLUMNS[columnIndex].setValue(row, aValue);
+                       fireTableRowsUpdated(rowIndex, rowIndex);
+               }
+
+               public void adjustColumnModel(TableColumnModel columnModel) {
+                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                                       .getLocalizedProperties("languages/appconfigdialog");
+                       int mx = columnModel.getColumnCount();
+                       for (int idx = 0; idx < mx; idx++) {
+                               String reskey = COLUMNS[idx].getResourceKey() + ".width";
+                               int width = Integer.parseInt(strings.getProperty(reskey));
+                               columnModel.getColumn(idx).setPreferredWidth(width);
+                       }
+               }
+       }
+
        public AppConfigDialog(JFrame parent) {
                super(parent, true);
                try {
@@ -83,21 +333,21 @@ public class AppConfigDialog extends JDialog {
                        });
 
                        initComponent();
-                       
+
                        loadData();
-                       
+
                } catch (RuntimeException ex) {
                        logger.log(Level.SEVERE, "appConfig construct failed.", ex);
                        dispose();
                        throw ex;
                }
        }
-       
+
        private void initComponent() {
-               
+
                Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
                                .getLocalizedProperties("languages/appconfigdialog");
-               
+
                setTitle(strings.getProperty("title"));
 
                Container contentPane = getContentPane();
@@ -108,10 +358,10 @@ public class AppConfigDialog extends JDialog {
                btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 45));
                GridBagLayout btnPanelLayout = new GridBagLayout();
                btnPanel.setLayout(btnPanelLayout);
-               
+
                GridBagConstraints gbc = new GridBagConstraints();
-               
-               Action actApply = new AbstractAction(strings.getProperty("btn.apply")) {
+
+               actApply = new AbstractAction(strings.getProperty("btn.apply")) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
                                onUpdate();
@@ -129,8 +379,15 @@ public class AppConfigDialog extends JDialog {
                                onSetupLocalization();
                        }
                };
-               
+
                chkResetDoNotAskAgain = new JCheckBox(strings.getProperty("chk.askForCharactersDir"));
+               chkResetDoNotAskAgain.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               // 保存ボタンの状態更新のため
+                               updateUIState();
+                       }
+               });
 
                gbc.gridx = 0;
                gbc.gridy = 0;
@@ -144,7 +401,7 @@ public class AppConfigDialog extends JDialog {
                gbc.weightx = 1.;
                gbc.weighty = 0.;
                btnPanel.add(chkResetDoNotAskAgain, gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 1;
                gbc.gridheight = 1;
@@ -166,68 +423,152 @@ public class AppConfigDialog extends JDialog {
                gbc.weightx = 1.;
                gbc.weighty = 0.;
                btnPanel.add(Box.createHorizontalGlue(), gbc);
-               
+
                gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;
                gbc.weightx = 0.;
                JButton btnApply = new JButton(actApply);
                btnPanel.add(btnApply, gbc);
-               
+
                gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 2;
                gbc.weightx = 0.;
                JButton btnCancel = new JButton(actCancel);
                btnPanel.add(btnCancel, gbc);
-               
+
                add(btnPanel, BorderLayout.SOUTH);
-               
-               setSize(350, 400);
+
+               setSize(400, 400);
                setLocationRelativeTo(getParent());
-               
+
                // Notes
                JLabel lblCaution = new JLabel(strings.getProperty("caution"), JLabel.CENTER);
                lblCaution.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
                lblCaution.setForeground(Color.red);
                contentPane.add(lblCaution, BorderLayout.NORTH);
-               
+
                // Model
                appConfigTableModel = new AppConfigTableModel();
 
                // JTable
                AppConfig appConfig = AppConfig.getInstance();
                final Color invalidBgColor = appConfig.getInvalidBgColor();
+
                appConfigTable = new JTable(appConfigTableModel) {
                        private static final long serialVersionUID = 1L;
+
                        @Override
                        public Component prepareRenderer(TableCellRenderer renderer,
                                        int row, int column) {
                                Component comp = super.prepareRenderer(renderer, row, column);
-                               AppConfigRowModel rowModel = appConfigTableModel.getRow(row);
-                               if (rowModel.isRejected()) {
+                               AppConfigRow configRow = appConfigTableModel.getItems().get(row);
+                               if (configRow.isRejected()) {
+                                       // 差し戻された項目は警告色とする
                                        comp.setBackground(invalidBgColor);
                                } else {
+                                       // そうでなければ標準色に戻す
                                        if (isCellSelected(row, column)) {
                                                comp.setBackground(getSelectionBackground());
                                        } else {
                                                comp.setBackground(getBackground());
                                        }
                                }
+
+                               if (configRow.isModified() && !configRow.isRejected()) {
+                                       // 変更行の色を警告色にする
+                                       // (ただし、rejectのものは背景色を変えているので何もしない)
+                                       comp.setForeground(invalidBgColor);
+                               } else {
+                                       // そうでなければ標準色に戻す
+                                       comp.setForeground(getForeground());
+                               }
                                return comp;
                        }
+
                        @Override
                        public String getToolTipText(MouseEvent event) {
                                int row = rowAtPoint(event.getPoint());
                                int col = columnAtPoint(event.getPoint());
-                               if (col == 0) {
-                                       AppConfigRowModel rowModel = appConfigTableModel.getRow(row);
-                                       return rowModel.getDisplayName();
+                               if (AppConfigTableModel.COLUMNS[col] == ColumnDef.NAME) {
+                                       // 最初の列の表示をツールチップとして表示させる
+                                       int modelRow = convertRowIndexToModel(row);
+                                       return appConfigTableModel.getItems().get(modelRow).getDisplayName();
                                }
                                return super.getToolTipText(event);
                        }
+
+                       // 1つの列で複数のデータタイプの編集を可能にする
+                       // Jtable with different types of cells depending on data type
+                       // https://stackoverflow.com/questions/16970824/jtable-with-different-types-of-cells-depending-on-data-type
+
+                       private Class<?> editingClass;
+
+                       @Override
+                       public TableCellRenderer getCellRenderer(int row, int column) {
+                               editingClass = null;
+                               int modelColumn = convertColumnIndexToModel(column);
+                               if (AppConfigTableModel.COLUMNS[modelColumn] == ColumnDef.VALUE) {
+                                       // VALUE列の場合
+                                       int modelRow = convertRowIndexToModel(row);
+                                       AppConfigRow rowData = appConfigTableModel.getItems().get(modelRow);
+
+                                       // 行のデータ型に対応するレンダラーを取得する
+                                       Class<?> dataType = rowData.getPropertyType();
+                                       TableCellRenderer renderer = getDefaultRenderer(dataType);
+                                       if (renderer != null) {
+                                               return renderer;
+                                       }
+                               }
+                               // VALUE列以外は、標準のまま (もしくはレンダラーがみつからない場合)
+                               return super.getCellRenderer(row, column);
+                       }
+
+                       @Override
+                       public TableCellEditor getCellEditor(int row, int column) {
+                               editingClass = null;
+                               int modelColumn = convertColumnIndexToModel(column);
+                               if (AppConfigTableModel.COLUMNS[modelColumn] == ColumnDef.VALUE) {
+                                       // VALUE列の場合
+                                       int modelRow = convertRowIndexToModel(row);
+                                       AppConfigRow rowData = appConfigTableModel.getItems().get(modelRow);
+
+                                       // 行のデータ型に対応するレンダラーを取得する
+                                       editingClass = rowData.getPropertyType();
+                                       return getDefaultEditor(editingClass);
+
+                               } else {
+                                       // VALUE列以外は、標準のまま
+                                       return super.getCellEditor(row, column);
+                               }
+                       }
+
+                       //  This method is also invoked by the editor when the value in the editor
+                       //  component is saved in the TableModel. The class was saved when the
+                       //  editor was invoked so the proper class can be created.
+
+                       @Override
+                       public Class<?> getColumnClass(int column) {
+                               return editingClass != null ? editingClass : super.getColumnClass(column);
+                       }
                };
-               appConfigTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+
                appConfigTable.setShowGrid(true);
-               appConfigTable.setGridColor(AppConfig.getInstance().getGridColor());
+               appConfigTable.setGridColor(appConfig.getGridColor());
                appConfigTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
+               appConfigTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+
+               // データタイプがColorの場合のセルレンダラーとエディタを設定する
+               appConfigTable.setDefaultRenderer(Color.class, new ColorCellRender());
+               appConfigTable.setDefaultEditor(Color.class, new ColorCellEditor());
+
                appConfigTableModel.adjustColumnModel(appConfigTable.getColumnModel());
+
+               appConfigTableModel.addTableModelListener(new TableModelListener() {
+                       @Override
+                       public void tableChanged(TableModelEvent e) {
+                               // テーブルが変更された場合、保存ボタンの活性やReject状態の変更のため
+                               updateUIState();
+                       }
+               });
+
                JScrollPane appConfigTableSP = new JScrollPane(appConfigTable);
                appConfigTableSP.setBorder(BorderFactory.createCompoundBorder(
                                BorderFactory.createEmptyBorder(0, 3, 0, 3),
@@ -235,7 +576,7 @@ public class AppConfigDialog extends JDialog {
                                );
                appConfigTableSP.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
                contentPane.add(appConfigTableSP, BorderLayout.CENTER);
-               
+
                // RootPane
                Toolkit tk = Toolkit.getDefaultToolkit();
                JRootPane rootPane = getRootPane();
@@ -245,21 +586,101 @@ public class AppConfigDialog extends JDialog {
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeAppConfigDialog");
                am.put("closeAppConfigDialog", actCancel);
 
+               // 保存ボタンの活性制御
+               updateUIState();
+       }
+
+       /**
+        * 保存ボタンの活性制御とテーブルの編集状態の表示の更新
+        * (テーブルが編集された場合に更新される)
+        */
+       protected void updateUIState() {
+               boolean hasModified = false;
+
+               // テーブルを走査して変更を確認する
+               for (AppConfigRow itemRow : appConfigTableModel.getItems()) {
+                       if (itemRow.isModified()) {
+                               // 変更あり
+                               hasModified = true;
+                       } else if (itemRow.isRejected()) {
+                               // 変更されていない状態であれば、差し戻し状態を解除する
+                               itemRow.setRejected(false);
+                       }
+               }
+
+               // キャラクターデータディレクトリの問い合わせ状態が変わっているか?
+               if (orgDoNotAskAgain != chkResetDoNotAskAgain.isSelected()) {
+                       hasModified = true;
+               }
+
                // 保存先が無効であれば適用ボタンを有効にしない.
+               AppConfig appConfig = AppConfig.getInstance();
                boolean enableSave = !appConfig.getPrioritySaveFileList().isEmpty();
-               btnApply.setEnabled(enableSave);
+
+               // 保存が有効であり、且つ、変更された行があれば保存ボタンを有効とする
+               actApply.setEnabled(enableSave && hasModified);
        }
-       
+
        private void loadData() {
-               Properties original = AppConfig.getInstance().getProperties();
-               appConfigTableModel.initModel(original);
 
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
+                               .getLocalizedProperties("languages/appconfigdialog");
+
+               // AppConfigへのアクセッサを取得する
+               PropertyAccessorMap accessorMap = BeanPropertiesUtilities.getPropertyAccessorMap(AppConfig.class);
+
+               AppConfig appConfig = AppConfig.getInstance();
+               accessorMap.setBean(appConfig);
+
+               List<AppConfigRow> items = new ArrayList<AppConfigRow>();
+               int fallbackOrder = 1000;
+               for (Map.Entry<String, PropertyAccessor> accessorEntry : accessorMap.entrySet()) {
+                       // プロパティ名と現在値を取得する
+                       String name = accessorEntry.getKey();
+                       PropertyAccessor accessor = accessorEntry.getValue();
+                       Object value = accessor.getValue();
+
+                       // リソースからプロパティ名に対応する表示名を取得する(なければプロパティ名のまま)
+                       String displayName = strings.getProperty(name, name);
+                       int pt = displayName.indexOf(";");
+                       String order = Integer.toString(fallbackOrder++);
+                       if (pt > 0) {
+                               order = displayName.substring(0, pt);
+                               displayName = displayName.substring(pt + 1);
+                       }
+
+                       // 行オブジェクト作成
+                       AppConfigRow rowItem = new AppConfigRow(name, accessor, value);
+                       rowItem.setDisplayName(displayName);
+                       rowItem.setOrder(order);
+
+                       items.add(rowItem);
+               }
+
+               // 表示順に並べる
+               Collections.sort(items, new Comparator<AppConfigRow>() {
+                       @Override
+                       public int compare(AppConfigRow o1, AppConfigRow o2) {
+                               int ret = o1.getOrder().compareTo(o2.getOrder());
+                               if (ret == 0) {
+                                       ret = o1.getDisplayName().compareTo(o2.getDisplayName());
+                               }
+                               if (ret == 0) {
+                                       ret = o1.getName().compareTo(o2.getName());
+                               }
+                               return ret;
+                       }
+               });
+
+               appConfigTableModel.setItems(items);
+
+               // 最後に使ったキャラクターデータディレクトリの自動選択設定
                try {
                        recentCharactersDir = RecentCharactersDir.load();
 
                        if (recentCharactersDir != null) {
                                File lastUseCharactersDir = recentCharactersDir.getLastUseCharacterDir();
-                               boolean enableLastUseCharacterDir = lastUseCharactersDir != null && lastUseCharactersDir.isDirectory(); 
+                               boolean enableLastUseCharacterDir = lastUseCharactersDir != null && lastUseCharactersDir.isDirectory();
                                boolean doNotAskAgain = enableLastUseCharacterDir && recentCharactersDir.isDoNotAskAgain();
                                chkResetDoNotAskAgain.setEnabled(enableLastUseCharacterDir);
                                chkResetDoNotAskAgain.setSelected(!doNotAskAgain);
@@ -269,6 +690,9 @@ public class AppConfigDialog extends JDialog {
                        recentCharactersDir = null;
                        logger.log(Level.WARNING, "RecentCharactersDir load failed.", ex);
                }
+
+               // 初期状態の保存
+               this.orgDoNotAskAgain = chkResetDoNotAskAgain.isSelected();
        }
 
        /**
@@ -283,7 +707,7 @@ public class AppConfigDialog extends JDialog {
                                JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.OK_OPTION) {
                        return;
                }
-               
+
                try {
                        File baseDir = ConfigurationDirUtilities.getUserDataDir();
                        SetupLocalization setup = new SetupLocalization(baseDir);
@@ -297,7 +721,7 @@ public class AppConfigDialog extends JDialog {
                        ErrorMessageHelper.showErrorDialog(this, ex);
                }
        }
-       
+
        protected void onClose() {
                if (appConfigTableModel.isModified()) {
                        Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
@@ -310,266 +734,257 @@ public class AppConfigDialog extends JDialog {
                }
                dispose();
        }
-       
+
+       /**
+        * AppConfigと、キャラクターデータディレクトリの起動時の選択有無の設定値を保存する。
+        */
        protected void onUpdate() {
-               
+
                if (appConfigTable.isEditing()) {
                        // 編集中ならば許可しない.
                        Toolkit tk = Toolkit.getDefaultToolkit();
                        tk.beep();
                        return;
                }
-               
+
                Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
                                .getLocalizedProperties("languages/appconfigdialog");
 
-               // 編集されたプロパティを取得する.
-               Properties props = appConfigTableModel.getProperties();
+               // 編集されたAppConfigの設定値を取得する. (変更のあるもののみ)
+               Map<String, Object> modifiedValues = new HashMap<String, Object>();
+               for (AppConfigRow rowItem : appConfigTableModel.getItems()) {
+                       if (rowItem.isModified()) {
+                               String name = rowItem.getName();
+                               Object value = rowItem.getValue();
+                               modifiedValues.put(name, value);
+                       }
+               }
 
-               // 編集されたプロパティが適用可能か検証する.
-               Set<String> rejectNames = AppConfig.checkProperties(props);
-               if (!rejectNames.isEmpty()) {
-                       // エラーがある場合
-                       appConfigTableModel.setRejectNames(rejectNames);
+               // キャラクターデータディレクトリの起動時の選択状態の変更状態
+               boolean updateRecentCharactersDir = (orgDoNotAskAgain != chkResetDoNotAskAgain.isSelected());
 
-                       JOptionPane.showMessageDialog(this, strings.getProperty("error.message"),
-                                       strings.getProperty("error.caption"), JOptionPane.ERROR_MESSAGE);
+               if (!updateRecentCharactersDir && modifiedValues.isEmpty()) {
+                       // 変更点がないので何もしない
                        return;
                }
 
-               try {
-                       // アプリケーション設定を更新し、保存する.
-                       AppConfig appConfig = AppConfig.getInstance();
-                       appConfig.update(props);
-                       appConfig.saveConfig();
-
-                       // キャラクターデータディレクトリの起動時の選択
-                       if (chkResetDoNotAskAgain.isEnabled()) {
-                               boolean doNotAskAgain = !chkResetDoNotAskAgain.isSelected();
-                               if (doNotAskAgain != recentCharactersDir.isDoNotAskAgain()) {
-                                       recentCharactersDir.setDoNotAskAgain(doNotAskAgain);
-                                       recentCharactersDir.saveRecents();
-                               }
+               // AppConfigの保存
+               if (!modifiedValues.isEmpty()) {
+                       // 編集されたプロパティが適用可能か検証する.
+                       Set<String> rejectNames = AppConfig.checkProperties(modifiedValues);
+                       if (!rejectNames.isEmpty()) {
+                               // エラーがある場合
+                               appConfigTableModel.setRejectNames(rejectNames);
+
+                               JOptionPane.showMessageDialog(this, strings.getProperty("error.message"),
+                                               strings.getProperty("error.caption"), JOptionPane.ERROR_MESSAGE);
+                               return;
                        }
 
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-                       return;
+                       try {
+                               // アプリケーション設定を更新し、保存する.
+                               AppConfig appConfig = AppConfig.getInstance();
+                               appConfig.update(modifiedValues);
+                               appConfig.saveConfig();
+
+                       } catch (Exception ex) {
+                               ErrorMessageHelper.showErrorDialog(this, ex);
+                               return;
+                       }
                }
-               
+
+               // キャラクターデータディレクトリの起動時の選択の保存
+               if (updateRecentCharactersDir) {
+                       try {
+                               if (chkResetDoNotAskAgain.isEnabled()) {
+                                       boolean doNotAskAgain = !chkResetDoNotAskAgain.isSelected();
+                                       if (doNotAskAgain != recentCharactersDir.isDoNotAskAgain()) {
+                                               recentCharactersDir.setDoNotAskAgain(doNotAskAgain);
+                                               recentCharactersDir.saveRecents();
+                                       }
+                               }
+                       } catch (Exception ex) {
+                               ErrorMessageHelper.showErrorDialog(this, ex);
+                               return;
+                       }
+               }
+
                // アプリケーションの再起動が必要なことを示すダイアログを表示する.
                String message = strings.getProperty("caution");
                JOptionPane.showMessageDialog(this, message);
 
                dispose();
        }
-       
 }
 
-class AppConfigRowModel implements Comparable<AppConfigRowModel> {
-
-       private Properties target;
-       
-       private String key;
-       
-       private String displayName;
-       
-       private boolean rejected;
-       
-       public AppConfigRowModel(Properties target, String key, String displayName) {
-               this.target = target;
-               this.key = key;
-               this.displayName = displayName;
+/**
+ * カラーセル
+ */
+class ColorCell extends JPanel {
+       private static final long serialVersionUID = 1L;
+
+       private String title = "Color";
+
+       private JPanel box;
+
+       private JLabel label;
+
+       private JButton button;
+
+       private ActionListener actionListener;
+
+       public ColorCell() {
+               this(null);
        }
 
-       @Override
-       public int hashCode() {
-               return key == null ? 0 : key.hashCode();
+       /**
+        * ボタンのアクションリスナを指定して構築する
+        * @param actionListener
+        */
+       public ColorCell(ActionListener actionListener) {
+               super(new BorderLayout());
+               this.actionListener = actionListener;
+
+               box = new JPanel(new BorderLayout());
+
+               label = new JLabel();
+               label.setHorizontalAlignment(SwingConstants.CENTER);
+               box.add(label, BorderLayout.CENTER);
+               box.setBorder(BorderFactory.createEtchedBorder());
+
+               button = new JButton();
+               Dimension dim = button.getPreferredSize();
+               dim.width = 24;
+               button.setPreferredSize(dim);
+               button.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               onClick(e);
+                       }
+               });
+
+               add(box, BorderLayout.CENTER);
+               add(button, BorderLayout.EAST);
+               setSelectedColor(Color.BLACK);
        }
-       
-       @Override
-       public boolean equals(Object obj) {
-               if (obj == this) {
-                       return true;
-               }
-               if (obj != null && obj instanceof AppConfigRowModel) {
-                       AppConfigRowModel o = (AppConfigRowModel) obj;
-                       return key == null ? o.key == null : key.equals(o.key);
-               }
-               return false;
+
+       public String getTitle() {
+               return title;
        }
-       
-       public int compareTo(AppConfigRowModel o) {
-               if (o == this) {
-                       return 0;
-               }
-               int ret = displayName.compareTo(o.displayName);
-               if (ret == 0) {
-                       ret = key.compareTo(o.key);
+
+       public void setTitle(String title) {
+               String old = this.title;
+               if (old == null ? title != null : !old.equals(title)) {
+                       this.title = title;
+                       firePropertyChange("title", old, title);
                }
-               return ret;
-       }
-       
-       public String getKey() {
-               return key;
        }
-       
-       public void setValue(String value) {
-               if (value == null) {
-                       value = "";
+
+       protected void onClick(ActionEvent e) {
+               // ※ カラー選択ダイアログは、Java7以降でないとアルファ値の設定はできない。
+               // Java6で実行するとアルファチャネルが消されたものになる。
+               // (設定ファイルとしては手作業では設定可能なので、とりあえず、このまま。)
+               Color selColor = JColorChooser.showDialog(ColorCell.this, title, selectedColor);
+               if (selColor != null) {
+                       setSelectedColor(selColor);
+                       if (actionListener != null) {
+                               actionListener.actionPerformed(e);
+                       }
                }
-               target.setProperty(key, value);
        }
-       
-       public String getValue() {
-               return target.getProperty(key);
+
+       private Color selectedColor;
+
+       public Color getSelectedColor() {
+               return selectedColor;
        }
 
-       public String getDisplayName() {
-               int sep = displayName.indexOf(';');
-               if (sep >= 0) {
-                       return displayName.substring(sep + 1);
+       public void setSelectedColor(Color color) {
+               if (color == null) {
+                       color = Color.BLACK;
+               }
+               Color old = this.selectedColor;
+               if (old == null ? color != null : !old.equals(color)) {
+                       this.selectedColor = color;
+
+                       Color colorForeground = new Color(color.getRGB() ^ 0xffffff).brighter();
+
+                       int alpha = color.getAlpha();
+
+                       // JPanelの背景色としてアルファの透過色をそのまま使うと
+                       // 親コンポーネントの背景色と混じり、色のカタログとして用をなさないので
+                       // 白を背景色とした合成済みに補正しておく
+                       // (アルファが255の場合はそのままで良い)
+                       Color premultipliedColor;
+                       if (alpha == 255) {
+                               premultipliedColor = color;
+                       } else {
+                               float[] rgb = color.getRGBColorComponents(null);
+                               float[] bgRgb = Color.WHITE.getRGBColorComponents(null); // 背景色 = 白色
+                               // アルファを合成済みにする
+                               float a = ((float) alpha) / 255f;
+                               rgb[0] = rgb[0] * a + bgRgb[0] * (1 - a);
+                               rgb[1] = rgb[1] * a + bgRgb[1] * (1 - a);
+                               rgb[2] = rgb[2] * a + bgRgb[2] * (1 - a);
+                               premultipliedColor = new Color(rgb[0], rgb[1], rgb[2]);
+                       }
+
+                       box.setBackground(premultipliedColor);
+                       label.setForeground(colorForeground);
+
+                       String msg;
+                       if (alpha != 255) {
+                               // アルファが255以外の場合はアルファ値も含めてARGBで表示する
+                               msg = String.format("#%08X", ((long) color.getRGB()) & 0xffffffffL);
+                       } else {
+                               // アルファが255の場合はRGBのみ表示する。
+                               msg = String.format("#%06X", ((long) color.getRGB()) & 0xffffffL);
+                       }
+                       label.setText(msg);
+
+                       firePropertyChange("selectedColor", old, color);
                }
-               return displayName;
-       }
-       
-       public boolean isRejected() {
-               return rejected;
-       }
-       
-       public void setRejected(boolean rejected) {
-               this.rejected = rejected;
        }
 }
 
-class AppConfigTableModel extends AbstractTableModelWithComboBoxModel<AppConfigRowModel> {
+/**
+ * カラーセルのレンダラー
+ */
+class ColorCellRender extends DefaultTableCellRenderer {
 
        private static final long serialVersionUID = 1L;
 
-       private static final String[] COLUMN_NAMES;
-       
-       private static final int[] COLUMN_WIDTHS;
-       
-       private Properties target = new Properties();
-       
-       private Properties original;
-       
-       static {
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                               .getLocalizedProperties("languages/appconfigdialog");
+       private ColorCell panel = new ColorCell();
 
-               COLUMN_NAMES = new String[] {
-                               strings.getProperty("column.key"),
-                               strings.getProperty("column.value"),
-               };
-               COLUMN_WIDTHS = new int[] {
-                               Integer.parseInt(strings.getProperty("column.key.width")),
-                               Integer.parseInt(strings.getProperty("column.value.width")),
-               };
+       @Override
+       public Component getTableCellRendererComponent(JTable table, Object value,
+                       boolean isSelected, boolean hasFocus, int row, int column) {
+               panel.setSelectedColor((Color) value);
+               return panel;
        }
-       
-       public void initModel(Properties original) {
-               clear();
-               target.clear();
-
-               this.original = original;
-               if (original != null) {
-                       target.putAll(original);
-                       
-                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                                       .getLocalizedProperties("languages/appconfigdialog");
+}
 
-                       for (Object key : target.keySet()) {
-                               String displayName = strings.getProperty((String) key);
-                               if (displayName == null || displayName.length() == 0) {
-                                       displayName = (String) key;
-                               }
-                               
-                               AppConfigRowModel rowModel = new AppConfigRowModel(target, (String) key, displayName);
-                               addRow(rowModel);
-                       }
-               }
+/**
+ * カラーセルを編集モードにした場合のエディタ
+ */
+class ColorCellEditor extends AbstractCellEditor implements TableCellEditor {
 
-               sort();
-       }
-       
-       public void sort() {
-               Collections.sort(elements);
-               fireTableDataChanged();
-       }
-       
-       public void setRejectNames(Set<String> rejectNames) {
-               for (AppConfigRowModel rowModel : elements) {
-                       String key = rowModel.getKey();
-                       boolean rejected = (rejectNames != null && rejectNames.contains(key));
-                       rowModel.setRejected(rejected);
-               }
-               fireTableDataChanged();
-       }
-       
-       /**
-        * 編集されているか?
-        * 
-        * @return 編集されていればtrue、そうでなければfalse
-        */
-       public boolean isModified() {
-               if (original == null) {
-                       return true;
-               }
-               return !original.equals(target);
-       }
-       
-       public Properties getProperties() {
-               return target;
-       }
-       
-       
-       public int getColumnCount() {
-               return COLUMN_NAMES.length;
-       }
-       
-       @Override
-       public Class<?> getColumnClass(int columnIndex) {
-               return String.class;
-       }
-       
-       @Override
-       public String getColumnName(int column) {
-               return COLUMN_NAMES[column];
-       }
-       
-       @Override
-       public boolean isCellEditable(int rowIndex, int columnIndex) {
-               if (columnIndex == 1) {
-                       return true;
-               }
-               return false;
-       }
-       
-       public Object getValueAt(int rowIndex, int columnIndex) {
-               AppConfigRowModel rowModel = getRow(rowIndex);
-               switch (columnIndex) {
-               case 0:
-                       return rowModel.getDisplayName();
-               case 1:
-                       return rowModel.getValue();
-               }
-               return "";
-       }
-       
-       @Override
-       public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
-               AppConfigRowModel rowModel = getRow(rowIndex);
-               if (columnIndex == 1) {
-                       rowModel.setValue((String) aValue);
-                       fireTableCellUpdated(rowIndex, columnIndex);
+       private static final long serialVersionUID = 1L;
+
+       private ColorCell colorCell = new ColorCell(new ActionListener() {
+               @Override
+               public void actionPerformed(ActionEvent e) {
+                       fireEditingStopped();
                }
+       });
+
+       public Component getTableCellEditorComponent(final JTable table, final Object value,
+                       final boolean isSelected, final int row, final int column) {
+               colorCell.setSelectedColor((Color) value);
+               return colorCell;
        }
-       
-       public void adjustColumnModel(TableColumnModel columnModel) {
-               int mx = columnModel.getColumnCount();
-               for (int idx = 0; idx < mx; idx++) {
-                       columnModel.getColumn(idx).setWidth(COLUMN_WIDTHS[idx]);
-               }
+
+       public Object getCellEditorValue() {
+               return colorCell.getSelectedColor();
        }
 }
index 8a36ca8..ceaa51c 100644 (file)
@@ -1308,7 +1308,7 @@ class PreviewImagePanel extends JPanel {
                if ((drawGridMask & bgColorMode.mask()) != 0) {
                        Color oldc = g.getColor();
                        try {
-                               g.setColor(new Color(appConfig.getPreviewGridColor(), true));
+                               g.setColor(appConfig.getPreviewGridColor());
                                drawGrid(g, imgRct.x, imgRct.y, appConfig.getPreviewGridSize());
 
                        } finally {
index 687c56b..4b740df 100644 (file)
@@ -5,6 +5,7 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -12,9 +13,12 @@ import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.AbstractMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -75,6 +79,168 @@ public final class BeanPropertiesUtilities {
        }
 
        /**
+        * プロパティへのアクセッサをまとめたもの。
+        * 実際にビーンにアクセスするためには、{@link #setBean(Object)}でビーンを設定する必要がある。
+        */
+       public static class PropertyAccessorMap extends AbstractMap<String, PropertyAccessor> {
+
+               private final BeanHolder beanHolder;
+
+               private final Map<String, PropertyAccessor> accessorMap;
+
+               public PropertyAccessorMap(Map<String, PropertyAccessor> accessorMap, BeanHolder beanHolder) {
+                       this.accessorMap = accessorMap;
+                       this.beanHolder = beanHolder;
+               }
+
+               public Object getBean() {
+                       return beanHolder.getBean();
+               }
+
+               public void setBean(Object bean) {
+                       beanHolder.setBean(bean);
+               }
+
+               @Override
+               public Set<Entry<String, PropertyAccessor>> entrySet() {
+                       return accessorMap.entrySet();
+               }
+       }
+
+       /**
+        * アクセッサからビーンを間接参照するためのホルダ。
+        * (実際にビーンにアクセスするまでビーンの設定を遅延させるため。)
+        */
+       public static class BeanHolder {
+
+               private Object bean;
+
+               public Object getBean() {
+                       return bean;
+               }
+
+               public void setBean(Object bean) {
+                       this.bean = bean;
+               }
+       }
+
+       /**
+        * プロパティへのアクセッサ
+        */
+       public interface PropertyAccessor {
+
+               /**
+                * プロパティのタイプ
+                * @return
+                */
+               Class<?> getPropertyType();
+
+               /**
+                * ビーンからプロパティを取得する。
+                * @return
+                */
+               Object getValue();
+
+               /**
+                * ビーンのプロパティを設定する。
+                * @param value
+                */
+               void setValue(Object value);
+
+               /**
+                * プロパティがもつアノテーションを取得する。
+                * @param annotationClass
+                * @return アノテーション、なければnull
+                */
+               <T extends Annotation> T getAnnotation(Class<T> annotationClass);
+       }
+
+       /**
+        * クラスを指定してプロパティアクセッサのマップを生成して返す。
+        * @param beanClass
+        * @return
+        * @throws IntrospectionException
+        */
+       public static PropertyAccessorMap getPropertyAccessorMap(final Class<?> beanClass) {
+               if (beanClass == null) {
+                       throw new NullPointerException("beanClass");
+               }
+               Map<String, PropertyAccessor> accessorMap = new TreeMap<String, PropertyAccessor>();
+               final BeanHolder beanHolder = new BeanHolder();
+
+               BeanInfo beanInfo;
+               try {
+                       beanInfo = Introspector.getBeanInfo(beanClass);
+               } catch (IntrospectionException ex) {
+                       throw new RuntimeException("bean intorospector failed. :" + beanClass, ex);
+               }
+               for (PropertyDescriptor propDesc : beanInfo
+                               .getPropertyDescriptors()) {
+                       String name = propDesc.getName();
+                       final Class<?> typ = propDesc.getPropertyType();
+                       final Method mtdReader = propDesc.getReadMethod();
+                       final Method mtdWriter = propDesc.getWriteMethod();
+
+                       if (mtdReader != null && mtdWriter != null) {
+                               // 読み書き双方が可能なもののみ対象とする.
+                               PropertyAccessor accessor = new PropertyAccessor() {
+                                       private Object getBean() {
+                                               Object bean = beanHolder.getBean();
+                                               if (bean == null) {
+                                                       throw new IllegalStateException("bean not set.");
+                                               }
+                                               return bean;
+                                       }
+
+                                       @Override
+                                       public Class<?> getPropertyType() {
+                                               return typ;
+                                       }
+
+                                       @Override
+                                       public Object getValue() {
+                                               try {
+                                                       return mtdReader.invoke(getBean());
+                                               } catch (RuntimeException ex) {
+                                                       throw ex;
+                                               } catch (Exception ex) {
+                                                       if (ex.getCause() instanceof RuntimeException) {
+                                                               throw (RuntimeException) ex.getCause();
+                                                       }
+                                                       throw new RuntimeException(ex);
+                                               }
+                                       }
+
+                                       @Override
+                                       public void setValue(Object value) {
+                                               try {
+                                                       mtdWriter.invoke(getBean(), value);
+                                               } catch (RuntimeException ex) {
+                                                       throw ex;
+                                               } catch (Exception ex) {
+                                                       if (ex.getCause() instanceof RuntimeException) {
+                                                               throw (RuntimeException) ex.getCause();
+                                                       }
+                                                       throw new RuntimeException(ex);
+                                               }
+                                       }
+
+                                       @Override
+                                       public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+                                               T annt = mtdReader.getAnnotation(annotationClass);
+                                               if (annt == null) {
+                                                       annt = mtdWriter.getAnnotation(annotationClass);
+                                               }
+                                               return annt;
+                                       }
+                               };
+                               accessorMap.put(name, accessor);
+                       }
+               }
+               return new PropertyAccessorMap(accessorMap, beanHolder);
+       }
+
+       /**
         * ビーンのSetter/Getterのペアをもつプロパティに対して、Propertiesより該当するプロパティの値を
         * 読み取り、プロパティに設定します.<br>
         * Propertiesに該当するプロパティ名が設定されていなければスキップされます.<br>
@@ -90,85 +256,82 @@ public final class BeanPropertiesUtilities {
                        throw new IllegalArgumentException();
                }
                HashSet<String> rejectNames = new HashSet<String>();
-               try {
-                       BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
-                       for (PropertyDescriptor propDesc : beanInfo
-                                       .getPropertyDescriptors()) {
-                               Class<?> typ = propDesc.getPropertyType();
-                               Method mtdReader = propDesc.getReadMethod();
-                               Method mtdWriter = propDesc.getWriteMethod();
-                               if (mtdReader != null && mtdWriter != null) {
-                                       // 読み書き双方が可能なもののみ対象とする.
-
-                                       String name = propDesc.getName();
-
-                                       // プロパティのStringConverterSpecアノテーションがあれば取得する
-                                       StringConverterSpec anntStringConverter = mtdReader.getAnnotation(StringConverterSpec.class);
-                                       if (anntStringConverter == null) {
-                                               anntStringConverter = mtdWriter.getAnnotation(StringConverterSpec.class);
-                                       }
 
-                                       String strVal = props.getProperty(name);
-                                       if (strVal == null) {
-                                               // 設定値がないのでスキップ
-                                               continue;
-                                       }
+               PropertyAccessorMap accessorMap = getPropertyAccessorMap(bean.getClass());
+               accessorMap.setBean(bean);
 
+               for (Map.Entry<String, PropertyAccessor> accessorEntry : accessorMap.entrySet()) {
+                       String name = accessorEntry.getKey();
+                       PropertyAccessor accessor = accessorEntry.getValue();
+                       Class<?> typ = accessor.getPropertyType();
+                       // プロパティのStringConverterSpecアノテーションがあれば取得する
+                       StringConverterSpec anntStringConverter = accessor.getAnnotation(StringConverterSpec.class);
 
-                                       Object val;
-                                       Throwable reject = null;
-                                       try {
-                                               if (anntStringConverter != null) {
-                                                       Class<? extends StringConverter> convCls = anntStringConverter.value();
-                                                       StringConverter conv = convCls.getConstructor().newInstance();
-                                                       val = conv.valueOf(strVal);
-                                               } else if (String.class.equals(typ)) {
-                                                       val = strVal;
-                                               } else if (strVal.length() == 0) {
-                                                       val = null;
-                                               } else {
-                                                       if (Boolean.class.equals(typ) || boolean.class.equals(typ)) {
-                                                               val = Boolean.valueOf(strVal);
-                                                       } else if (Integer.class.equals(typ) || int.class.equals(typ)) {
-                                                               val = Integer.valueOf(strVal);
-                                                       } else if (Long.class.equals(typ) || long.class.equals(typ)) {
-                                                               val = Long.valueOf(strVal);
-                                                       } else if (Float.class.equals(typ) || float.class.equals(typ)) {
-                                                               val = Float.valueOf(strVal);
-                                                       } else if (Double.class.equals(typ) || double.class.equals(typ)) {
-                                                               val = Double.valueOf(strVal);
-                                                       } else if (BigInteger.class.equals(typ)) {
-                                                               val = new BigInteger(strVal);
-                                                       } else if (BigDecimal.class.equals(typ)) {
-                                                               val = new BigDecimal(strVal);
-                                                       } else if (Color.class.equals(typ)) {
-                                                               val = Color.decode(strVal);
-                                                       } else {
-                                                               rejectNames.add(name);
-                                                               logger.log(Level.WARNING,
-                                                                       "unsupported propery type: " + typ
-                                                                       + "/beanClass=" + bean.getClass() + " #" + name);
-                                                               continue;
-                                                       }
-                                               }
-                                               mtdWriter.invoke(bean, val);
-                                               reject = null;
+                       String strVal = props.getProperty(name);
+                       if (strVal == null) {
+                               // 設定値がないのでスキップ
+                               continue;
+                       }
 
-                                       } catch (Exception ex) {
-                                               reject = ex;
-                                       }
 
-                                       if (reject != null) {
+                       Object val;
+                       Throwable reject = null;
+                       try {
+                               if (anntStringConverter != null) {
+                                       Class<? extends StringConverter> convCls = anntStringConverter.value();
+                                       StringConverter conv = convCls.getConstructor().newInstance();
+                                       val = conv.valueOf(strVal);
+                               } else if (String.class.equals(typ)) {
+                                       val = strVal;
+                               } else if (strVal.length() == 0) {
+                                       val = null;
+                               } else {
+                                       if (Boolean.class.equals(typ) || boolean.class.equals(typ)) {
+                                               val = Boolean.valueOf(strVal);
+                                       } else if (Integer.class.equals(typ) || int.class.equals(typ)) {
+                                               val = Integer.valueOf(strVal);
+                                       } else if (Long.class.equals(typ) || long.class.equals(typ)) {
+                                               val = Long.valueOf(strVal);
+                                       } else if (Float.class.equals(typ) || float.class.equals(typ)) {
+                                               val = Float.valueOf(strVal);
+                                       } else if (Double.class.equals(typ) || double.class.equals(typ)) {
+                                               val = Double.valueOf(strVal);
+                                       } else if (BigInteger.class.equals(typ)) {
+                                               val = new BigInteger(strVal);
+                                       } else if (BigDecimal.class.equals(typ)) {
+                                               val = new BigDecimal(strVal);
+                                       } else if (Color.class.equals(typ)) {
+                                               long decode = Long.decode(strVal).longValue();
+                                               if ((decode & 0xff000000) != 0) {
+                                                       // アルファの指定あり
+                                                       val = new Color((int) decode, true);
+                                               } else {
+                                                       // アルファの指定なし
+                                                       // (仕組み上、アルファ0の設定値は受け取れないが実用性に問題ないと思われる。)
+                                                       val = new Color((int) decode, false);
+                                               }
+                                       } else {
                                                rejectNames.add(name);
-                                               logger.log(Level.WARNING, "invalid propery: "
-                                                               + typ + "/beanClass="
-                                                               + bean.getClass() + " #" + name + " /val=" + strVal
-                                                               , reject);
+                                               logger.log(Level.WARNING,
+                                                       "unsupported propery type: " + typ
+                                                       + "/beanClass=" + bean.getClass() + " #" + name);
+                                               continue;
                                        }
                                }
+                               accessor.setValue(val);
+                               reject = null;
+
+                       } catch (Exception ex) {
+                               reject = ex;
+                       }
+
+                       if (reject != null) {
+                               rejectNames.add(name);
+                               logger.log(Level.WARNING, "invalid propery: "
+                                               + typ + "/beanClass="
+                                               + bean.getClass() + " #" + name + " /val=" + strVal
+                                               , reject);
                        }
-               } catch (IntrospectionException ex) {
-                       throw new RuntimeException("bean intorospector failed. :" + bean.getClass(), ex);
                }
                return rejectNames;
        }
@@ -185,53 +348,51 @@ public final class BeanPropertiesUtilities {
                }
 
                try {
-                       BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
-                       for (PropertyDescriptor propDesc : beanInfo.getPropertyDescriptors()) {
-                               Method mtdReader = propDesc.getReadMethod();
-                               Method mtdWriter = propDesc.getWriteMethod();
-                               if (mtdReader != null && mtdWriter != null) {
-                                       // 読み書き双方が可能なもののみ対象とする.
-
-                                       // プロパティのStringConverterSpecアノテーションがあれば取得する
-                                       StringConverterSpec anntStringConverter = mtdReader.getAnnotation(StringConverterSpec.class);
-                                       if (anntStringConverter == null) {
-                                               anntStringConverter = mtdWriter.getAnnotation(StringConverterSpec.class);
-                                       }
+                       PropertyAccessorMap accessorMap = getPropertyAccessorMap(bean.getClass());
+                       accessorMap.setBean(bean);
+
+                       for (Map.Entry<String, PropertyAccessor> accessorEntry : accessorMap.entrySet()) {
+                               String name = accessorEntry.getKey();
+                               PropertyAccessor accessor = accessorEntry.getValue();
+
+                               // プロパティのStringConverterSpecアノテーションがあれば取得する
+                               StringConverterSpec anntStringConverter = accessor.getAnnotation(StringConverterSpec.class);
+
+                               Object val = accessor.getValue();
+
+                               String strVal;
+                               if (anntStringConverter != null) {
+                                       StringConverter conv = anntStringConverter.value().getConstructor().newInstance();
+                                       strVal = conv.toString(val);
 
-                                       String name = propDesc.getName();
-                                       Object val = mtdReader.invoke(bean);
-
-                                       String strVal;
-                                       if (anntStringConverter != null) {
-                                               StringConverter conv = anntStringConverter.value().getConstructor().newInstance();
-                                               strVal = conv.toString(val);
-
-                                       } else if (val == null) {
-                                               strVal = "";
-                                       } else if (val instanceof String) {
-                                               strVal = (String) val;
-                                       } else if (val instanceof Number) {
-                                               strVal = ((Number) val).toString();
-                                       } else if (val instanceof Boolean) {
-                                               strVal = ((Boolean) val).booleanValue() ? "true" : "false";
-                                       } else if (val instanceof Color) {
-                                               strVal = "#" + Integer.toHexString(((Color) val).getRGB() & 0xffffff);
+                               } else if (val == null) {
+                                       strVal = "";
+                               } else if (val instanceof String) {
+                                       strVal = (String) val;
+                               } else if (val instanceof Number) {
+                                       strVal = ((Number) val).toString();
+                               } else if (val instanceof Boolean) {
+                                       strVal = ((Boolean) val).booleanValue() ? "true" : "false";
+                               } else if (val instanceof Color) {
+                                       Color color = (Color) val;
+                                       int alpha = color.getAlpha();
+                                       if (alpha == 255) {
+                                               strVal = "#" + Integer.toHexString(color.getRGB() & 0xffffff);
                                        } else {
-                                               logger.log(Level.WARNING, "unsupported propery type: "
-                                                               + val.getClass() + "/beanClass="
-                                                               + bean.getClass() + " #" + name);
-                                               continue;
+                                               strVal = "#" + Long.toHexString(color.getRGB() & 0xffffffffL);
                                        }
-
-                                       props.setProperty(name, strVal);
+                               } else {
+                                       logger.log(Level.WARNING, "unsupported propery type: "
+                                                       + val.getClass() + "/beanClass="
+                                                       + bean.getClass() + " #" + name);
+                                       continue;
                                }
+
+                               props.setProperty(name, strVal);
                        }
 
-               } catch (IntrospectionException ex) {
-                       throw new RuntimeException("bean intorospector failed. :" + bean.getClass(), ex);
                } catch (Exception ex) {
                        throw new RuntimeException("bean property read failed. :" + bean.getClass(), ex);
                }
        }
-
 }
index 5d2b864..19d707e 100644 (file)
@@ -9,8 +9,8 @@
 <entry key="chk.askForCharactersDir">Ask the data directory during startup.</entry>
 <entry key="column.key">Key</entry>
 <entry key="column.value">Value</entry>
-<entry key="column.key.width">200</entry>
-<entry key="column.value.width">100</entry>
+<entry key="column.key.width">300</entry>
+<entry key="column.value.width">200</entry>
 <entry key="confirm.close.caption">Confirm</entry>
 <entry key="confirm.close">Are you sure want to close?</entry>
 <entry key="error.caption">Error</entry>
index d1b272d..37196c8 100644 (file)
@@ -9,8 +9,8 @@
 <entry key="chk.askForCharactersDir">起動時にデータフォルダを選択する.</entry>
 <entry key="column.key">プロパティ名</entry>
 <entry key="column.value">設定値</entry>
-<entry key="column.key.width">200</entry>
-<entry key="column.value.width">100</entry>
+<entry key="column.key.width">300</entry>
+<entry key="column.value.width">200</entry>
 <entry key="confirm.close.caption">確認</entry>
 <entry key="confirm.close">編集を破棄しますか?</entry>
 <entry key="error.caption">エラー</entry>
index e0176ff..0c939cf 100644 (file)
@@ -9,8 +9,8 @@
 <entry key="chk.askForCharactersDir">启动时询问工作路径</entry>
 <entry key="column.key">键</entry>
 <entry key="column.value">值</entry>
-<entry key="column.key.width">200</entry>
-<entry key="column.value.width">100</entry>
+<entry key="column.key.width">300</entry>
+<entry key="column.value.width">200</entry>
 <entry key="confirm.close.caption">确认</entry>
 <entry key="confirm.close">你确定要关闭么?</entry>
 <entry key="error.caption">错误</entry>