OSDN Git Service

カテゴリでパーツの選択を省略可能な設定ができるように修正
authorseraphy <seraphy@users.osdn.me>
Tue, 11 Dec 2018 16:44:29 +0000 (01:44 +0900)
committerseraphy <seraphy@users.osdn.me>
Tue, 11 Dec 2018 16:44:29 +0000 (01:44 +0900)
12 files changed:
src/main/java/charactermanaj/model/PartsCategory.java
src/main/java/charactermanaj/model/io/CharacterDataXMLReader.java
src/main/java/charactermanaj/model/io/CharacterDataXMLWriter.java
src/main/java/charactermanaj/ui/ImageSelectPanel.java
src/main/java/charactermanaj/ui/MainFrame.java
src/main/java/charactermanaj/ui/ProfileEditDialog.java
src/main/java/charactermanaj/ui/model/PartsSelectionManager.java
src/main/resources/languages/profileditdialog.xml
src/main/resources/languages/profileditdialog_ja.xml
src/main/resources/languages/profileditdialog_zh.xml
src/main/resources/menu/menu_ja.xml
src/main/resources/schema/character.xsd

index 7324744..3ce2f3e 100644 (file)
@@ -33,6 +33,11 @@ public final class PartsCategory implements Comparable<PartsCategory> {
        private final boolean multipleSelectable;
 
        /**
+        * 省略可能?
+        */
+       private final boolean optional;
+
+       /**
         * 表示行数
         */
        private final int visibleRows;
@@ -48,11 +53,12 @@ public final class PartsCategory implements Comparable<PartsCategory> {
         * @param categoryId カテゴリ識別名
         * @param localizedCategoryName カテゴリ表示名
         * @param multipleSelectable 複数選択可能?
+        * @param optional 省略可能?
         * @param visibleRows 表示行数
         * @param layers レイヤー情報の配列、nullの場合は空とみなす
         */
        public PartsCategory(final int order, final String categoryId, String localizedCategoryName,
-                       boolean multipleSelectable, int visibleRows, Layer[] layers) {
+                       boolean multipleSelectable, boolean optional, int visibleRows, Layer[] layers) {
                if (categoryId == null || categoryId.trim().length() == 0) {
                        throw new IllegalArgumentException();
                }
@@ -66,6 +72,7 @@ public final class PartsCategory implements Comparable<PartsCategory> {
                this.categoryId = categoryId.trim();
                this.localizedCategoryName = localizedCategoryName.trim();
                this.multipleSelectable = multipleSelectable;
+               this.optional = optional;
                this.layers = Collections.unmodifiableList(Arrays.asList(layers.clone()));
                this.visibleRows = visibleRows;
        }
@@ -163,6 +170,14 @@ public final class PartsCategory implements Comparable<PartsCategory> {
        }
 
        /**
+        * 省略可能であるか?
+        * @return 省略可能であるか?
+        */
+       public boolean isOptional() {
+               return optional;
+       }
+
+       /**
         * 表示行数を取得する.
         * @return 表示行数
         */
index 5699c6d..c1604e9 100644 (file)
@@ -248,6 +248,17 @@ public class CharacterDataXMLReader {
                                        boolean multipleSelectable = Boolean.parseBoolean(catElm
                                                        .getAttribute("multipleSelectable"));
 
+                                       boolean optional;
+                                       String strOptional = catElm.getAttribute("optional");
+                                       if (strOptional != null && strOptional.trim().length() > 0) {
+                                               optional = Boolean.parseBoolean(strOptional);
+                                       } else {
+                                               // ver0.999以前にはないのでmultipleSelectableから自動導出する
+                                               // 複数選択カテゴリの場合は0以上なので省略可とする。
+                                               // 複数選択カテゴリでなければ必須とする。
+                                               optional = multipleSelectable;
+                                       }
+
                                        String categoryDisplayName = getLocalizedElementText(
                                                        catElm, "display-name", lang);
 
@@ -324,7 +335,7 @@ public class CharacterDataXMLReader {
 
                                        PartsCategory category = new PartsCategory(
                                                        categories.size(), categoryId, categoryDisplayName,
-                                                       multipleSelectable, visibleRows,
+                                                       multipleSelectable, optional, visibleRows,
                                                        layers.toArray(new Layer[layers.size()]));
                                        categories.add(category);
                                }
index 13e2c44..5a408f4 100644 (file)
@@ -176,8 +176,11 @@ public class CharacterDataXMLWriter {
                        // category
                        Element nodeCategory = doc.createElementNS(NS, "category");
                        nodeCategory.setAttribute("id", category.getCategoryId());
+
                        nodeCategory.setAttribute("multipleSelectable",
                                        category.isMultipleSelectable() ? "true" : "false");
+                       nodeCategory.setAttribute("optional",
+                                       category.isOptional() ? "true" : "false");
 
                        // visible-rows
                        Element nodeVisibleRows = doc.createElementNS(NS, "visible-rows");
index a3b5264..1d6c5f3 100644 (file)
@@ -340,7 +340,7 @@ public class ImageSelectPanel extends JPanel {
                                evaluatePopup(e);
                        }
                        private void evaluatePopup(MouseEvent e) {
-                               if ((partsCategory.isMultipleSelectable() || isDeselectableSingleCategory())
+                               if ((partsCategory.isOptional() || isDeselectableAlways())
                                                && e.isPopupTrigger()) {
                                        partsSelectTablePopupMenu.show(partsSelectTable, e.getX(), e.getY());
                                }
@@ -952,16 +952,16 @@ public class ImageSelectPanel extends JPanel {
         * 選択選択パーツカテゴリの選択解除を許可するか?<br>
         * @return 許可する場合はtrue
         */
-       public boolean isDeselectableSingleCategory() {
-               return partsSelectTableModel.isDeselectableSingleCategory();
+       public boolean isDeselectableAlways() {
+               return partsSelectTableModel.isDeselectableAlways();
        }
 
        /**
         * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>
         * @param deselectable 許可する場合はtrue
         */
-       public void setDeselectableSingleCategory(boolean deselectable) {
-               partsSelectTableModel.setDeselectableSingleCategory(deselectable);
+       public void setDeselectableAlways(boolean deselectable) {
+               partsSelectTableModel.setDeselectableAlways(deselectable);
        }
 }
 
@@ -1062,7 +1062,7 @@ class PartsSelectListModel extends AbstractTableModel {
        /**
         * カテゴリが複数パーツでない場合でも選択解除を許可するフラグ.
         */
-       private boolean deselectableSingleCategory;
+       private boolean deselectableAlways;
 
        public PartsSelectListModel(PartsCategory partsCategory) {
                if (partsCategory == null) {
@@ -1114,16 +1114,16 @@ class PartsSelectListModel extends AbstractTableModel {
         * 選択選択パーツカテゴリの選択解除を許可するか?<br>
         * @return 許可する場合はtrue
         */
-       public boolean isDeselectableSingleCategory() {
-               return deselectableSingleCategory;
+       public boolean isDeselectableAlways() {
+               return deselectableAlways;
        }
 
        /**
         * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>
         * @param deselectable 許可する場合はtrue
         */
-       public void setDeselectableSingleCategory(boolean deselectable) {
-               this.deselectableSingleCategory = deselectable;
+       public void setDeselectableAlways(boolean deselectableAlways) {
+               this.deselectableAlways = deselectableAlways;
        }
 
        public PartsSelectRow getRow(int rowIndex) {
@@ -1185,10 +1185,10 @@ class PartsSelectListModel extends AbstractTableModel {
                PartsSelectRow rowModel = partsSelectRowList.get(rowIndex);
                boolean checked = ((Boolean) aValue).booleanValue();
 
-               if (!checked && rowModel.isChecked() && !deselectableSingleCategory
-                               && !partsCategory.isMultipleSelectable()) {
-                       // 複数選択が可能でない場合、現在選択中のチェックは一つしかないはずのため、これを外すことはしない。
-                       // ã\81\9fã\81 ã\81\97å\8d\98ä¸\80é\81¸æ\8a\9eã\83\91ã\83¼ã\83\84カテゴリでの選択解除が許可されている場合を除く。
+               if (!checked && rowModel.isChecked() && !deselectableAlways
+                               && !partsCategory.isOptional()) {
+                       // 省略可能でない場合、これを外すことはしない。
+                       // ã\81\9fã\81 ã\81\97å\85¨カテゴリでの選択解除が許可されている場合を除く。
                        return;
                }
 
index c40a720..a9521a3 100644 (file)
@@ -2681,9 +2681,8 @@ public class MainFrame extends JFrame
         * 単一選択カテゴリのパーツの解除を許可する。
         */
        protected void onDeselectableAllCategory() {
-               partsSelectionManager
-                               .setDeselectableSingleCategory( !partsSelectionManager
-                                               .isDeselectableSingleCategory());
+               partsSelectionManager.setDeselectableAlways(
+                               !partsSelectionManager.isDeselectableAlways());
        }
 
        /**
@@ -2906,7 +2905,7 @@ public class MainFrame extends JFrame
                        public void menuSelected(MenuEvent e) {
                                menuBuilder.getJMenuItem("edit.copy").setEnabled(previewPane.getPreviewImage() != null);
                                menuBuilder.getJMenuItem("edit.deselectparts").setSelected(
-                                               partsSelectionManager.isDeselectableSingleCategory());
+                                               partsSelectionManager.isDeselectableAlways());
                                menuBuilder.getJMenuItem("edit.enableAutoShrink").setSelected(minimizeMode);
                                menuBuilder.getJMenuItem("edit.enableZoomBox").setSelected(previewPane.isVisibleZoomBox());
                        }
index b539215..fbf92a0 100644 (file)
@@ -93,9 +93,9 @@ public class ProfileEditDialog extends JDialog {
        protected static class JTextFieldEx extends JTextField {
 
                private static final long serialVersionUID = -8608404290439184405L;
-               
+
                private boolean error;
-               
+
                @Override
                public Color getBackground() {
                        if (error) {
@@ -104,26 +104,26 @@ public class ProfileEditDialog extends JDialog {
                        }
                        return super.getBackground();
                }
-               
+
                public void setError(boolean error) {
                        if (this.error != error) {
                                this.error = error;
                                repaint();
                        }
                }
-               
+
                public boolean isError() {
                        return error;
                }
        }
-       
+
 
        /**
         * オリジナルのデータ.
         */
        private CharacterData original;
 
-       
+
        /**
         * キャラクターデータID
         */
@@ -139,32 +139,32 @@ public class ProfileEditDialog extends JDialog {
         */
        private JTextField txtCharacterDocBase;
 
-       
+
        /**
         * キャラクター名
         */
        private JTextFieldEx txtCharacterName;
-       
+
        /**
         * イメージ幅
         */
        private JSpinner txtImageWidth;
-       
+
        /**
         * イメージ高さ
         */
        private JSpinner txtImageHeight;
-       
+
        /**
         * 作者
         */
        private JTextField txtAuthor;
-       
+
        /**
         * 説明
         */
        private JTextArea txtDescription;
-       
+
        /**
         * ディレクトリの監視
         */
@@ -180,38 +180,38 @@ public class ProfileEditDialog extends JDialog {
         * カテゴリのモデル
         */
        private CategoriesTableModel categoriesTableModel;
-       
+
        /**
         * レイヤーのモデル
         */
        private LayersTableModel layersTableModel;
-       
+
        /**
         * パーツセットのモデル
         */
        private PartssetsTableModel partssetsTableModel;
-       
+
        /**
         * お勧めリンクのモデル
         */
        private RecommendationTableModel recommendationsTableModel;
-       
-       
+
+
        /**
         * 画面の内容から生成された新しいキャラクターデータ、もしくはnull
         */
        private CharacterData result;
-       
+
 
        /**
         * OKボタン
         */
        private JButton btnOK;
-       
+
 
        /**
         * キャラクターデータの編集画面を構築する.<br>
-        * 
+        *
         * @param parent
         *            親、もしくはnull
         * @param original
@@ -221,10 +221,10 @@ public class ProfileEditDialog extends JDialog {
                super(parent, true);
                initDialog(parent, original);
        }
-       
+
        /**
         * キャラクターデータの編集画面を構築する.<br>
-        * 
+        *
         * @param parent
         *            親、もしくはnull
         * @param original
@@ -237,7 +237,7 @@ public class ProfileEditDialog extends JDialog {
 
        /**
         * 編集ダイアログを初期化する.
-        * 
+        *
         * @param origianl
         *            編集もとキャラクター定義
         */
@@ -247,7 +247,7 @@ public class ProfileEditDialog extends JDialog {
                        throw new IllegalArgumentException();
                }
                this.original = original;
-               
+
                // ウィンドウイベントのハンドル
                setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
                addWindowListener(new WindowAdapter() {
@@ -270,22 +270,22 @@ public class ProfileEditDialog extends JDialog {
                        title = strings.getProperty("title.new");
                }
                setTitle(title);
-               
+
                // OK/CANCEL PANEL
-               
+
                JPanel buttonsPanel = new JPanel();
                buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));
                GridBagLayout buttonsPanelLayout = new GridBagLayout();
                buttonsPanel.setLayout(buttonsPanelLayout);
                GridBagConstraints gbc = new GridBagConstraints();
-       
+
                String okCaption;
                if (original.isValid()) {
                        okCaption = strings.getProperty("button.ok.edit");
                } else {
                        okCaption = strings.getProperty("button.ok.new");
                }
-               
+
                btnOK = new JButton(new AbstractAction(okCaption) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
@@ -293,7 +293,7 @@ public class ProfileEditDialog extends JDialog {
                        }
                });
                btnOK.setEnabled(false); // 初期状態はディセーブル、updateUIStateで更新する.
-               
+
                Action actOpenDir = new AbstractAction(strings.getProperty("button.openDir")) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
@@ -310,7 +310,7 @@ public class ProfileEditDialog extends JDialog {
                        }
                };
                JButton btnCancel = new JButton(actCancel);
-       
+
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.weightx = 0.;
@@ -329,17 +329,17 @@ public class ProfileEditDialog extends JDialog {
                gbc.gridy = 0;
                gbc.weightx = 0.;
                buttonsPanel.add(btnOK, gbc);
-               
+
                gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 3;
                gbc.gridy = 0;
                buttonsPanel.add(btnCancel, gbc);
-               
+
                Container contentPane = getContentPane();
                contentPane.setLayout(new BorderLayout());
                contentPane.add(buttonsPanel, BorderLayout.SOUTH);
 
                // InputMap/ActionMap
-               
+
                Toolkit tk = Toolkit.getDefaultToolkit();
                JRootPane rootPane = getRootPane();
                rootPane.setDefaultButton(btnOK);
@@ -348,12 +348,12 @@ public class ProfileEditDialog extends JDialog {
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeWindow");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeWindow");
                rootPane.getActionMap().put("closeWindow", actCancel);
-               
+
                // Main
                JPanel mainPanel = new JPanel();
                GridBagLayout mainPanelLayout = new GridBagLayout();
                mainPanel.setLayout(mainPanelLayout);
-               
+
                this.txtCharacterID = new JTextFieldEx();
                this.txtCharacterRev = new JTextFieldEx();
                this.txtCharacterDocBase = new JTextField();
@@ -370,7 +370,7 @@ public class ProfileEditDialog extends JDialog {
                                Integer.MAX_VALUE, 1)); // 現実に可能であるかを問わず制限を設けない
                this.txtAuthor = new JTextField();
                this.txtDescription = new JTextArea();
-               
+
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.gridwidth = 1;
@@ -402,7 +402,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                mainPanel.add(new JLabel(strings.getProperty("id.caption"), SwingConstants.RIGHT), gbc);
-               
+
                txtCharacterID.setToolTipText(strings.getProperty("id.caption.help"));
                txtCharacterRev.setToolTipText(strings.getProperty("rev.caption.help"));
 
@@ -415,7 +415,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                mainPanel.add(txtCharacterID, gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 2;
                gbc.gridwidth = 1;
@@ -436,8 +436,8 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                mainPanel.add(txtCharacterRev, gbc);
-               
-               
+
+
                gbc.gridx = 0;
                gbc.gridy = 3;
                gbc.gridwidth = 1;
@@ -539,19 +539,19 @@ public class ProfileEditDialog extends JDialog {
                mainPanel.add(new JScrollPane(txtDescription), gbc);
 
                // model
-               
+
                this.colorGroupsTableModel = new ColorGroupsTableModel();
                this.categoriesTableModel = new CategoriesTableModel();
                this.layersTableModel = new LayersTableModel();
                this.partssetsTableModel = new PartssetsTableModel();
                this.recommendationsTableModel = new RecommendationTableModel();
-               
+
                this.colorGroupsTableModel.setEditable(true);
                this.categoriesTableModel.setEditable(true);
                this.layersTableModel.setEditable(true);
                this.partssetsTableModel.setEditable(true);
                this.recommendationsTableModel.setEditable(true);
-               
+
                // colorGroup
                JPanel colorGroupPanel = new JPanel(new BorderLayout());
                final JTable colorGroupTable = new JTable(colorGroupsTableModel);
@@ -561,7 +561,7 @@ public class ProfileEditDialog extends JDialog {
                colorGroupTable.setRowHeight(colorGroupTable.getRowHeight() + 4);
                colorGroupTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                colorGroupPanel.add(new JScrollPane(colorGroupTable), BorderLayout.CENTER);
-               
+
                JPanel colorGroupBtnPanel = new JPanel();
                GridBagLayout colorGroupBtnPanelLayout = new GridBagLayout();
                colorGroupBtnPanel.setLayout(colorGroupBtnPanelLayout);
@@ -606,8 +606,8 @@ public class ProfileEditDialog extends JDialog {
                                }
                        }
                };
-               
-               
+
+
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.gridwidth = 1;
@@ -617,7 +617,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                colorGroupBtnPanel.add(new JButton(actColorGroupAdd), gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 1;
                colorGroupBtnPanel.add(new JButton(actColorGroupDel), gbc);
@@ -636,7 +636,7 @@ public class ProfileEditDialog extends JDialog {
                colorGroupBtnPanel.add(Box.createGlue(), gbc);
 
                colorGroupPanel.add(colorGroupBtnPanel, BorderLayout.EAST);
-               
+
                final Color disabledForeground = appConfig.getDisabledCellForgroundColor();
 
                // categories
@@ -673,13 +673,13 @@ public class ProfileEditDialog extends JDialog {
                categoriesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                categoriesTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
                categoriesPanel.add(new JScrollPane(categoriesTable), BorderLayout.CENTER);
-               
+
                categoriesTableModel.adjustColumnModel(categoriesTable.getColumnModel());
-               
+
                JPanel categoriesBtnPanel = new JPanel();
                GridBagLayout categoryBtnPanelLayout = new GridBagLayout();
                categoriesBtnPanel.setLayout(categoryBtnPanelLayout);
-               
+
                AbstractAction actCategoryAdd = new AbstractAction(strings.getProperty("categories.add.caption")) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
@@ -730,7 +730,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                categoriesBtnPanel.add(new JButton(actCategoryAdd), gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 1;
                categoriesBtnPanel.add(new JButton(actCategoryDel), gbc);
@@ -750,7 +750,7 @@ public class ProfileEditDialog extends JDialog {
 
                categoriesPanel.add(categoriesBtnPanel, BorderLayout.EAST);
 
-               
+
                // layers
                JPanel layersPanel = new JPanel(new BorderLayout());
                final Color invalidBgColor = appConfig.getInvalidBgColor();
@@ -761,12 +761,12 @@ public class ProfileEditDialog extends JDialog {
                        public Component prepareRenderer(TableCellRenderer renderer,
                                        int row, int column) {
                                Component comp = super.prepareRenderer(renderer, row, column);
-                               
+
                                if (comp instanceof JCheckBox) {
                                        // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer
                                        comp.setEnabled(isCellEditable(row, column) && isEnabled());
                                }
-                               
+
                                LayersTableModel model = (LayersTableModel) getModel();
                                LayersTableRow layer = model.getRow(row);
                                comp.setForeground(getForeground());
@@ -775,7 +775,7 @@ public class ProfileEditDialog extends JDialog {
                                // 前景色、ディセーブル時は灰色
                                Color foregroundColor = getForeground();
                                comp.setForeground(isEnabled() ? foregroundColor : disabledForeground);
-                               
+
                                return comp;
                        }
                };
@@ -785,7 +785,7 @@ public class ProfileEditDialog extends JDialog {
                                new FirstItemInjectionComboBoxModelWrapper<ColorGroupsTableRow>(colorGroupsTableModel, ColorGroupsTableRow.NA));
                JComboBox categoriesCombo = new JComboBox(categoriesTableModel);
                JComboBox colorModelsCombo = new JComboBox(ColorModels.values());
-               
+
                layersTable.setShowGrid(true);
                layersTable.setGridColor(appConfig.getGridColor());
                layersTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
@@ -797,11 +797,11 @@ public class ProfileEditDialog extends JDialog {
                layersTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
                layersTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                layersPanel.add(new JScrollPane(layersTable), BorderLayout.CENTER);
-               
+
                JPanel layersBtnPanel = new JPanel();
                GridBagLayout layersBtnPanelLayout = new GridBagLayout();
                layersBtnPanel.setLayout(layersBtnPanelLayout);
-               
+
                AbstractAction actLayerAdd = new AbstractAction(strings.getProperty("layers.add.caption")) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
@@ -843,7 +843,7 @@ public class ProfileEditDialog extends JDialog {
                                }
                        }
                };
-               
+
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.gridwidth = 1;
@@ -853,7 +853,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                layersBtnPanel.add(new JButton(actLayerAdd), gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 1;
                layersBtnPanel.add(new JButton(actLayerDel), gbc);
@@ -876,10 +876,10 @@ public class ProfileEditDialog extends JDialog {
                layersBtnPanel.add(Box.createGlue(), gbc);
 
                layersPanel.add(layersBtnPanel, BorderLayout.EAST);
-               
+
                chkWatchDir = new JCheckBox(strings.getProperty("layers.watchdir"));
                layersPanel.add(chkWatchDir, BorderLayout.SOUTH);
-               
+
                // Presets
                JPanel partssetsPanel = new JPanel(new BorderLayout());
                JTable partssetsTable = new JTable(partssetsTableModel) {
@@ -955,7 +955,7 @@ public class ProfileEditDialog extends JDialog {
                JPanel recommendationsBtnPanel = new JPanel();
                GridBagLayout recommendationsBtnPanelLayout = new GridBagLayout();
                recommendationsBtnPanel.setLayout(recommendationsBtnPanelLayout);
-               
+
                AbstractAction actRecommendationAdd = new AbstractAction(strings.getProperty("recommendations.add.caption")) {
                        private static final long serialVersionUID = 1L;
                        public void actionPerformed(ActionEvent e) {
@@ -991,7 +991,7 @@ public class ProfileEditDialog extends JDialog {
                                }
                        }
                };
-               
+
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.gridwidth = 1;
@@ -1001,7 +1001,7 @@ public class ProfileEditDialog extends JDialog {
                gbc.anchor = GridBagConstraints.EAST;
                gbc.fill = GridBagConstraints.BOTH;
                recommendationsBtnPanel.add(new JButton(actRecommendationAdd), gbc);
-               
+
                gbc.gridx = 0;
                gbc.gridy = 1;
                recommendationsBtnPanel.add(new JButton(actRecommendationDel), gbc);
@@ -1020,11 +1020,11 @@ public class ProfileEditDialog extends JDialog {
                recommendationsBtnPanel.add(Box.createGlue(), gbc);
 
                recommendationsPanel.add(recommendationsBtnPanel, BorderLayout.EAST);
-               
-               
+
+
                // データのロード
                loadCharacterData(original);
-               
+
                // レイヤーのカテゴリ使用状態を監視するリスナ関連
                final HashMap<CategoriesTableRow, List<LayersTableRow>> usedLayerMap = new HashMap<CategoriesTableRow, List<LayersTableRow>>();
                final Runnable resetUsedLayers = new Runnable() {
@@ -1061,7 +1061,7 @@ public class ProfileEditDialog extends JDialog {
                                return usedLayerMap.get(partsCategory);
                        }
                });
-               
+
                // 生成可能であるかチェックするためのリスナ
                layersTableModel.addListDataListener(new ListDataListener() {
                        public void contentsChanged(ListDataEvent e) {
@@ -1075,7 +1075,7 @@ public class ProfileEditDialog extends JDialog {
                                updateUIState();
                        }
                });
-               
+
                // キャラクターID/REV/NAMEが変更されたことを通知され、OKボタンを判定するためのリスナ
                DocumentListener textChangeListener = new DocumentListener() {
                        public void removeUpdate(DocumentEvent e) {
@@ -1091,9 +1091,9 @@ public class ProfileEditDialog extends JDialog {
                txtCharacterID.getDocument().addDocumentListener(textChangeListener);
                txtCharacterRev.getDocument().addDocumentListener(textChangeListener);
                txtCharacterName.getDocument().addDocumentListener(textChangeListener);
-               
+
                // TABS
-               
+
                JTabbedPane tabbedPane = new JTabbedPane();
                tabbedPane.add(strings.getProperty("panel.basicinfomation"), mainPanel);
                tabbedPane.add(strings.getProperty("panel.colorgroup"), colorGroupPanel);
@@ -1101,18 +1101,18 @@ public class ProfileEditDialog extends JDialog {
                tabbedPane.add(strings.getProperty("panel.layers"), layersPanel);
                tabbedPane.add(strings.getProperty("panel.partssets"), partssetsPanel);
                tabbedPane.add(strings.getProperty("panel.recommendations"), recommendationsPanel);
-               
+
                contentPane.add(tabbedPane, BorderLayout.CENTER);
-               
+
                setSize(500, 500);
                setLocationRelativeTo(parent);
 
                updateUIState();
        }
-       
+
        /**
         * CharacterDataから画面へ転記する.
-        * 
+        *
         * @param original
         *            オリジナル情報
         */
@@ -1130,7 +1130,7 @@ public class ProfileEditDialog extends JDialog {
                // 基本情報
                txtCharacterID.setText(original.getId());
                txtCharacterRev.setText(original.getRev());
-               
+
                txtCharacterDocBase.setText(original.getDocBase() == null ? "" : original.getDocBase().toString());
 
                txtCharacterName.setText(original.getName());
@@ -1149,7 +1149,7 @@ public class ProfileEditDialog extends JDialog {
                                colorGroupMap.put(colorGroup, mutableColorGroup);
                        }
                }
-               
+
                // カテゴリとレイヤー
                for (PartsCategory partsCategory : original.getPartsCategories()) {
                        categoriesTableModel.addRow(new CategoriesTableRow(partsCategory));
@@ -1169,10 +1169,10 @@ public class ProfileEditDialog extends JDialog {
                                layersTableModel.addRow(editableLayer);
                        }
                }
-               
+
                // ディレクトリ監視有無
                chkWatchDir.setSelected(original.isWatchDirectory());
-               
+
                // パーツセット
                ArrayList<PartsSet> partsSets = new ArrayList<PartsSet>();
                partsSets.addAll(original.getPartsSets().values());
@@ -1197,7 +1197,7 @@ public class ProfileEditDialog extends JDialog {
                        }
                }
        }
-       
+
 
        protected void onOpenDir() {
                try {
@@ -1209,13 +1209,13 @@ public class ProfileEditDialog extends JDialog {
                        ErrorMessageHelper.showErrorDialog(this, ex);
                }
        }
-       
+
        /**
         * 画面を閉じる場合
         */
        protected void onClose() {
                result = null;
-               
+
                boolean writable = !original.isValid() || original.canWrite(); // 新規または更新可能
                if (writable) {
                        final Properties strings = LocalizedResourcePropertyLoader
@@ -1228,7 +1228,7 @@ public class ProfileEditDialog extends JDialog {
                }
                dispose();
        }
-       
+
        /**
         * 画面の状態を更新する
         */
@@ -1245,18 +1245,18 @@ public class ProfileEditDialog extends JDialog {
        }
 
        private interface ValidationReport {
-               
+
                void validateReport(JComponent comp, boolean valid);
-               
+
        }
 
        /**
         * 入力データが有効であるか判定する.
-        * 
+        *
         * @return 有効であればtrue
         */
        protected boolean isValidData(ValidationReport report) {
-               
+
                // ID, REVが英数字であるか判定
                Pattern pat = Pattern.compile("\\p{Graph}+");
                String id = txtCharacterID.getText().trim();
@@ -1289,10 +1289,10 @@ public class ProfileEditDialog extends JDialog {
                        report.validateReport(txtCharacterRev, validRev);
                        report.validateReport(txtCharacterName, validName);
                }
-               
+
                return validLayers && validId && validRev && validName;
        }
-       
+
        /**
         * OKボタン押下
         */
@@ -1305,7 +1305,7 @@ public class ProfileEditDialog extends JDialog {
                }
 
                // 編集可能であり、且つ、保存可能な状態であれば
-               CharacterData newCd = createCharacterData(); 
+               CharacterData newCd = createCharacterData();
                final Properties strings = LocalizedResourcePropertyLoader
                        .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);
                if (original.isValid() && !original.isSameStructure(newCd)) {
@@ -1314,7 +1314,7 @@ public class ProfileEditDialog extends JDialog {
                                int ret = JOptionPane.showConfirmDialog(this,
                                        strings.get("confirm.needchangerevision"),
                                        strings.getProperty("confirm"),
-                                       JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); 
+                                       JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
                                if (ret == JOptionPane.CANCEL_OPTION) {
                                        return;
                                }
@@ -1324,7 +1324,7 @@ public class ProfileEditDialog extends JDialog {
                                        CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
                                        newCd.setRev(persist.generateRev());
                                }
-                               
+
                        } else if ( !newCd.isUpperCompatibleStructure(original)){
                                // 上位互換のない構造が変更されていることを通知する.
                                if (JOptionPane.showConfirmDialog(this,
@@ -1339,10 +1339,10 @@ public class ProfileEditDialog extends JDialog {
                dispose();
                return;
        }
-       
+
        /**
         * 画面の情報からキャラクターデータを構築して返します.<br>
-        * 
+        *
         * @return キャラクターデータ
         */
        protected CharacterData createCharacterData() {
@@ -1350,24 +1350,24 @@ public class ProfileEditDialog extends JDialog {
 
                // オリジナルのDocBaseVを転記する.
                cd.setDocBase(original.getDocBase());
-               
+
                // ID, REV
                cd.setId(txtCharacterID.getText().trim());
                cd.setRev(txtCharacterRev.getText().trim());
 
                // キャラクターセット名
                cd.setName(txtCharacterName.getText().trim());
-               
+
                // 情報
                cd.setAuthor(txtAuthor.getText().trim());
                cd.setDescription(txtDescription.getText());
-               
+
                // サイズ
                Dimension imageSize = new Dimension();
                imageSize.width = ((Number)(txtImageWidth.getValue())).intValue();
                imageSize.height = ((Number)(txtImageHeight.getValue())).intValue();
                cd.setImageSize(imageSize);
-               
+
                // カラーグループ
                int mxColorGroup = colorGroupsTableModel.getRowCount();
                ArrayList<ColorGroup> colorGroups = new ArrayList<ColorGroup>();
@@ -1375,7 +1375,7 @@ public class ProfileEditDialog extends JDialog {
                        colorGroups.add(colorGroupsTableModel.getRow(idx).convert());
                }
                cd.setColorGroups(colorGroups);
-               
+
                // レイヤーの構築
                HashMap<CategoriesTableRow, List<Layer>> layerMap = new HashMap<CategoriesTableRow, List<Layer>>();
                int mxLayer = layersTableModel.getRowCount();
@@ -1392,7 +1392,7 @@ public class ProfileEditDialog extends JDialog {
                                layers.add(layer);
                        }
                }
-               
+
                // カテゴリおよびレイヤー
                ArrayList<PartsCategory> categories = new ArrayList<PartsCategory>();
                int mxCategory = categoriesTableModel.getRowCount();
@@ -1408,7 +1408,7 @@ public class ProfileEditDialog extends JDialog {
 
                // ディレクトリの監視
                cd.setWatchDirectory(chkWatchDir.isSelected());
-               
+
                // パーツセット情報
                int mxPartssets = partssetsTableModel.getRowCount();
                for (int idx = 0; idx < mxPartssets; idx++) {
@@ -1416,7 +1416,7 @@ public class ProfileEditDialog extends JDialog {
                        cd.addPartsSet(partsSet);
                }
                cd.setDefaultPartsSetId(partssetsTableModel.getDefaultPartsSetId());
-               
+
                // お気に入りリンク情報
                int mxRecommendations = recommendationsTableModel.getRowCount();
                ArrayList<RecommendationURL> recommendationURLList = new ArrayList<RecommendationURL>();
@@ -1441,55 +1441,55 @@ public class ProfileEditDialog extends JDialog {
                        recommendationURLList = null;
                }
                cd.setRecommendationURLList(recommendationURLList);
-               
+
                return cd;
        }
-       
+
        /**
         * 結果を取得する. キャンセルされた場合はnullが返される.<br>
-        * 
+        *
         * @return キャラクターデータ、またはnull
         */
        public CharacterData getResult() {
                return result;
        }
-       
+
 }
 
 /**
  * 編集用カラーグループ
- * 
+ *
  * @author seraphy
  */
 class ColorGroupsTableRow {
 
        private final String id;
-       
+
        private final boolean enabled;
-       
+
        private String localizedName;
 
        public static final ColorGroupsTableRow NA = new ColorGroupsTableRow("n/a", "", false);
-       
-       
+
+
        public ColorGroupsTableRow(final String id, final String localizedName) {
                this(id, localizedName, true);
        }
-       
+
        public static ColorGroupsTableRow valueOf(ColorGroup colorGroup) {
                if (colorGroup == null || !colorGroup.isEnabled()) {
                        return NA;
                }
                return new ColorGroupsTableRow(colorGroup.getId(), colorGroup.getLocalizedName(), true);
        }
-       
+
        public ColorGroup convert() {
                if (!isEnabled()) {
                        return ColorGroup.NA;
                }
                return new ColorGroup(getId(), getLocalizedName());
        }
-       
+
        private ColorGroupsTableRow(final String id, final String localizedName, final boolean enabled) {
                if (id == null || id.trim().length() == 0) {
                        throw new IllegalArgumentException();
@@ -1498,7 +1498,7 @@ class ColorGroupsTableRow {
                this.localizedName = (localizedName == null || localizedName.trim().length() == 0) ? id : localizedName;
                this.enabled = enabled;
        }
-       
+
        public void setLocalizedName(String localizedName) {
                if (localizedName == null || localizedName.trim().length() == 0) {
                        throw new IllegalArgumentException();
@@ -1508,24 +1508,24 @@ class ColorGroupsTableRow {
                }
                this.localizedName = localizedName;
        }
-       
+
        public boolean isEnabled() {
                return enabled;
        }
-       
+
        public String getId() {
                return id;
        }
-       
+
        public String getLocalizedName() {
                return localizedName;
        }
-       
+
        @Override
        public int hashCode() {
                return id.hashCode();
        }
-       
+
        @Override
        public boolean equals(Object obj) {
                if (obj == this) {
@@ -1537,7 +1537,7 @@ class ColorGroupsTableRow {
                }
                return false;
        }
-       
+
        public static boolean equals(ColorGroupsTableRow v1, ColorGroupsTableRow v2) {
                if (v1 == v2) {
                        return true;
@@ -1547,7 +1547,7 @@ class ColorGroupsTableRow {
                }
                return v1.equals(v2);
        }
-       
+
        @Override
        public String toString() {
                return getLocalizedName();
@@ -1558,7 +1558,7 @@ class ColorGroupsTableRow {
 
 /**
  * カラーグループのテーブル編集モデル
- * 
+ *
  * @author seraphy
  */
 class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGroupsTableRow> {
@@ -1566,7 +1566,7 @@ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGro
        private static final long serialVersionUID = 2952439955567262351L;
 
        private static final String[] colorGroupColumnNames;
-       
+
        private static final Logger logger = Logger.getLogger(ColorGroupsTableModel.class.getName());
 
        static {
@@ -1576,13 +1576,13 @@ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGro
                                strings.getProperty("colorgroup.column.colorgroupname"),
                };
        }
-       
+
        private int serialCounter = 1;
-       
+
        public int getColumnCount() {
                return colorGroupColumnNames.length;
        }
-       
+
        @Override
        public String getColumnName(int column) {
                return colorGroupColumnNames[column];
@@ -1596,7 +1596,7 @@ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGro
                }
                return "****";
        }
-       
+
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                ColorGroupsTableRow colorGroup = elements.get(rowIndex);
@@ -1612,18 +1612,18 @@ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGro
                                return;
                        }
                        fireTableCellUpdated(rowIndex, columnIndex);
-                       
+
                } catch (Exception ex) {
                        logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex);
                        // 無視する
                }
        }
-       
+
        @Override
        public Class<?> getColumnClass(int columnIndex) {
                return String.class;
        }
-       
+
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
                return isEditable();
@@ -1639,24 +1639,24 @@ class ColorGroupsTableModel extends AbstractTableModelWithComboBoxModel<ColorGro
 
 /**
  * カラーグループ用のコンボボックスモデルに対して最初のアイテムとしてN/Aを常に追加するモデルに変換するラップクラス.<br>
- * 
+ *
  * @author seraphy
  */
 class FirstItemInjectionComboBoxModelWrapper<T> implements ComboBoxModel {
-       
+
        private ComboBoxModel parent;
-       
+
        private T selectedItem;
-       
+
        private T firstItem;
-       
+
        private LinkedList<ListDataListener> listDataListeners = new LinkedList<ListDataListener>();
 
        public FirstItemInjectionComboBoxModelWrapper(ComboBoxModel parent, T firstItem) {
                if (parent == null || firstItem == null) {
                        throw new IllegalArgumentException();
                }
-               
+
                this.parent = parent;
                this.firstItem = firstItem;
 
@@ -1672,7 +1672,7 @@ class FirstItemInjectionComboBoxModelWrapper<T> implements ComboBoxModel {
                        }
                        /**
                         * 親コンボボックスモデルのインデックスを+1したイベントに変換する.
-                        * 
+                        *
                         * @param e
                         *            元イベント
                         * @return インデックス変換後のイベント
@@ -1701,7 +1701,7 @@ class FirstItemInjectionComboBoxModelWrapper<T> implements ComboBoxModel {
                        listener.intervalRemoved(e);
                }
        }
-       
+
        public Object getSelectedItem() {
                return selectedItem;
        }
@@ -1744,12 +1744,12 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
         * 順序
         */
        private int order;
-       
+
        /**
         * カテゴリ識別名
         */
        private String categoryId;
-       
+
        /**
         * カテゴリ表示名
         */
@@ -1759,20 +1759,25 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
         * 複数選択可能?
         */
        private boolean multipleSelectable;
-       
+
+       /**
+        * 省略可能?
+        */
+       private boolean optional;
+
        /**
         * 表示行数
         */
        private int visibleRows;
-       
+
        /**
         * レイヤー情報
         */
        private ArrayList<Layer> layers = new ArrayList<Layer>();
-       
+
        /**
         * カテゴリを構築する.<br>
-        * 
+        *
         * @param categoryId
         *            カテゴリ識別名
         * @param localizedCategoryName
@@ -1783,7 +1788,7 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
         *            レイヤー情報の配列
         */
        public CategoriesTableRow(final int order, final String categoryId, String localizedCategoryName,
-                       boolean multipleSelectable, int visibleRows, Layer[] layers) {
+                       boolean multipleSelectable, boolean optional, int visibleRows, Layer[] layers) {
                if (categoryId == null || categoryId.trim().length() == 0) {
                        throw new IllegalArgumentException();
                }
@@ -1797,10 +1802,11 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                this.categoryId = categoryId.trim();
                this.localizedCategoryName = localizedCategoryName.trim();
                this.multipleSelectable = multipleSelectable;
+               this.optional = optional;
                this.layers.addAll(Arrays.asList(layers));
                this.visibleRows = visibleRows;
        }
-       
+
        public CategoriesTableRow(PartsCategory partsCategory) {
                if (partsCategory == null) {
                        throw new IllegalArgumentException();
@@ -1809,16 +1815,17 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                this.categoryId = partsCategory.getCategoryId();
                this.localizedCategoryName = partsCategory.getLocalizedCategoryName();
                this.multipleSelectable = partsCategory.isMultipleSelectable();
+               this.optional = partsCategory.isOptional();
                this.layers.addAll(partsCategory.getLayers());
                this.visibleRows = partsCategory.getVisibleRows();
        }
-       
+
        public PartsCategory convert() {
                return new PartsCategory(order, categoryId, localizedCategoryName,
-                               multipleSelectable, visibleRows, layers
+                               multipleSelectable, optional, visibleRows, layers
                                                .toArray(new Layer[layers.size()]));
        }
-       
+
        public int compareTo(CategoriesTableRow o) {
                if (o == this) {
                        return 0;
@@ -1832,7 +1839,7 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                return ret;
        }
-       
+
        @Override
        public boolean equals(Object obj) {
                if (obj == this) {
@@ -1843,7 +1850,7 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                return false;
        }
-       
+
        public static boolean equals(CategoriesTableRow o1, CategoriesTableRow o2) {
                if (o1 == o2) {
                        return true;
@@ -1853,10 +1860,10 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                return o1.equals(o2);
        }
-       
+
        /**
         * 定義順を取得する
-        * 
+        *
         * @return 定義順
         */
        public int getOrder() {
@@ -1865,17 +1872,17 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
 
        /**
         * 定義順を設定する
-        * 
+        *
         * @param order
         *            定義順
         */
        public void setOrder(int order) {
                this.order = order;
        }
-       
+
        /**
         * 複数選択可能であるか?
-        * 
+        *
         * @return 複数選択可能であるか?
         */
        public boolean isMultipleSelectable() {
@@ -1884,7 +1891,7 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
 
        /**
         * 複数選択可能であるか設定する
-        * 
+        *
         * @param multipleSelectable
         *            複数選択可能であればtrue
         */
@@ -1893,8 +1900,24 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
        }
 
        /**
+        * 省略可能か?
+        * @return
+        */
+       public boolean isOptional() {
+               return optional;
+       }
+
+       /**
+        * 省略可能か設定する。
+        * @param optional
+        */
+       public void setOptional(boolean optional) {
+               this.optional = optional;
+       }
+
+       /**
         * 表示行数を取得する.
-        * 
+        *
         * @return 表示行数
         */
        public int getVisibleRows() {
@@ -1903,17 +1926,17 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
 
        /**
         * 表示行数を設定する
-        * 
+        *
         * @param visibleRows
         *            表示行数
         */
        public void setVisibleRows(int visibleRows) {
                this.visibleRows = visibleRows;
        }
-       
+
        /**
         * このカテゴリに指定したレイヤーが含まれるか検証する.
-        * 
+        *
         * @param layer
         *            レイヤー
         * @return 含まれる場合はtrue、含まれない場合はfalse
@@ -1928,19 +1951,19 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                return false;
        }
-       
+
        /**
         * レイヤー情報
-        * 
+        *
         * @return レイヤー情報
         */
        public Collection<Layer> getLayers() {
                return Collections.unmodifiableCollection(layers);
        }
-       
+
        /**
         * レイヤー情報
-        * 
+        *
         * @param layers
         */
        public void setLayers(Collection<Layer> layers) {
@@ -1949,11 +1972,11 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                        this.layers.addAll(layers);
                }
        }
-       
+
        /**
         * レイヤーを取得する.<br>
         * 該当するレイヤーがなければnull
-        * 
+        *
         * @param layerId
         *            レイヤー名
         * @return レイヤーもしくはnull
@@ -1969,28 +1992,28 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                return null;
        }
-       
+
        /**
         * カテゴリ識別名を取得する.
-        * 
+        *
         * @return カテゴリ識別名
         */
        public String getCategoryId() {
                return categoryId;
        }
-       
+
        /**
         * カテゴリ表示名を取得する.
-        * 
+        *
         * @return カテゴリ表示名
         */
        public String getLocalizedCategoryName() {
                return this.localizedCategoryName;
        }
-       
+
        /**
         * カテゴリ表示名を設定する.
-        * 
+        *
         * @param localizedCategoryName
         *            カテゴリ表示名
         */
@@ -2000,68 +2023,132 @@ class CategoriesTableRow implements Comparable<CategoriesTableRow> {
                }
                this.localizedCategoryName = localizedCategoryName.trim();
        }
-       
+
        @Override
        public int hashCode() {
                return this.categoryId.hashCode();
        }
-       
+
        @Override
        public String toString() {
                return getLocalizedCategoryName();
        }
-
 }
 
 /**
  * カテゴリのテーブル編集モデル.
- * 
+ *
  * @author seraphy
- * 
+ *
  */
 class CategoriesTableModel extends AbstractTableModelWithComboBoxModel<CategoriesTableRow> {
-       
+
        private static final long serialVersionUID = 1L;
-       
+
        private static final Logger logger = Logger.getLogger(CategoriesTableModel.class.getName());
 
        public interface UsedCategoryDetector {
-               
+
                List<LayersTableRow> getLayers(CategoriesTableRow partsCategory);
-               
        }
-       
-       private static final String[] categoriesColumnName;
-       
-       private static final int[] categoriesColumnWidths;
-       
-       static {
-               final Properties strings = LocalizedResourcePropertyLoader
-                               .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE);
-               categoriesColumnName = new String[] {
-                               strings.getProperty("categories.column.categoryname"),
-                               strings.getProperty("categories.column.multipleselectable"),
-                               strings.getProperty("categories.column.displayrowcount"),
-                               strings.getProperty("categories.column.usedlayers"),
-               };
-               categoriesColumnWidths = new int[] {
-                               Integer.parseInt(strings.getProperty("categories.column.categoryname.width")),
-                               Integer.parseInt(strings.getProperty("categories.column.multipleselectable.width")),
-                               Integer.parseInt(strings.getProperty("categories.column.displayrowcount.width")),
-                               Integer.parseInt(strings.getProperty("categories.column.usedlayers.width")),
+
+       enum ColumnDef {
+               CATEGORY_NAME("categories.column.categoryname", true, String.class) {
+                       @Override
+                       public Object getValue(CategoriesTableRow row) {
+                               return row.getLocalizedCategoryName();
+                       }
+                       @Override
+                       public void setValue(CategoriesTableRow row, Object aValue) {
+                               row.setLocalizedCategoryName((String) aValue);
+                       }
+               },
+               MULTIPLE_SELECTABLE("categories.column.multipleselectable", true, Boolean.class) {
+                       @Override
+                       public Object getValue(CategoriesTableRow row) {
+                               return Boolean.valueOf(row.isMultipleSelectable());
+                       }
+                       @Override
+                       public void setValue(CategoriesTableRow row, Object aValue) {
+                               row.setMultipleSelectable(((Boolean) aValue).booleanValue());
+                       }
+               },
+               OPTIONAL("categories.column.optional", true, Boolean.class) {
+                       @Override
+                       public Object getValue(CategoriesTableRow row) {
+                               return Boolean.valueOf(row.isOptional());
+                       }
+                       @Override
+                       public void setValue(CategoriesTableRow row, Object aValue) {
+                               row.setOptional(((Boolean) aValue).booleanValue());
+                       }
+               },
+               DISPLAY_ROW_COUNT("categories.column.displayrowcount", true, Integer.class) {
+                       @Override
+                       public Object getValue(CategoriesTableRow row) {
+                               return row.getVisibleRows();
+                       }
+                       @Override
+                       public void setValue(CategoriesTableRow row, Object aValue) {
+                               row.setVisibleRows(((Number) aValue).intValue());
+                       }
+               },
+               USED_LAYERS("categories.column.usedlayers", false, String.class) {
+                       @Override
+                       public Object getValue(CategoriesTableRow row) {
+                               return null;
+                       }
                };
+
+               private final String reskey;
+
+               private final boolean editable;
+
+               private final Class<?> dataType;
+
+               ColumnDef(String reskey, boolean editable, Class<?> dataType) {
+                       this.reskey = reskey;
+                       this.editable = editable;
+                       this.dataType = dataType;
+               }
+
+               public Class<?> getDataType() {
+                       return dataType;
+               }
+
+               public boolean isEditable() {
+                       return editable;
+               }
+
+               public String getResourceKey() {
+                       return reskey;
+               }
+
+               public abstract Object getValue(CategoriesTableRow row);
+
+               public void setValue(CategoriesTableRow row, Object value) {
+                       throw new UnsupportedOperationException(name());
+               }
        }
-       
+
+       private static final Properties strings = LocalizedResourcePropertyLoader
+                       .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE);
+
+       private static final ColumnDef[] COLUMNS = ColumnDef.values();
+
        private int serialCounter = 1;
 
        private UsedCategoryDetector usedCategoryDetector;
-       
+
        public CategoriesTableModel() {
        }
-       
+
        public void adjustColumnModel(TableColumnModel columnModel) {
-               for (int idx = 0; idx < categoriesColumnWidths.length; idx++) {
-                       columnModel.getColumn(idx).setPreferredWidth(categoriesColumnWidths[idx]);
+               for (int idx = 0; idx < COLUMNS.length; idx++) {
+                       ColumnDef columnDef = COLUMNS[idx];
+                       String reskey = columnDef.getResourceKey();
+                       int width = Integer.parseInt(strings.getProperty(reskey + ".width"));
+                       columnModel.getColumn(idx).setPreferredWidth(width);
                }
        }
 
@@ -2069,19 +2156,19 @@ class CategoriesTableModel extends AbstractTableModelWithComboBoxModel<Categorie
                        UsedCategoryDetector usedCategoryDetector) {
                this.usedCategoryDetector = usedCategoryDetector;
        }
-       
+
        public UsedCategoryDetector getUsedCategoryDetector() {
                return usedCategoryDetector;
        }
-       
+
        public void addCategory() {
                String id = "cat" + UUID.randomUUID().toString();
                String name = "Category" + (serialCounter++);
                CategoriesTableRow partsCategory = new CategoriesTableRow(
-                               serialCounter, id, name, false, 10, null);
+                               serialCounter, id, name, false, true, 10, null);
                addRow(partsCategory);
        }
-       
+
        /**
         * 定義順を振り直す.
         */
@@ -2093,40 +2180,36 @@ class CategoriesTableModel extends AbstractTableModelWithComboBoxModel<Categorie
                }
                fireTableDataChanged();
        }
-       
+
        @Override
        public int moveDown(int rowIndex) {
                int ret = super.moveDown(rowIndex);
                reorder();
                return ret;
        }
-       
+
        @Override
        public int moveUp(int rowIndex) {
                int ret = super.moveUp(rowIndex);
                reorder();
                return ret;
        }
-       
+
        @Override
        public String getColumnName(int column) {
-               return categoriesColumnName[column];
+               String reskey = COLUMNS[column].getResourceKey();
+               return strings.getProperty(reskey, reskey);
        }
-       
+
        public int getColumnCount() {
-               return categoriesColumnName.length;
+               return COLUMNS.length;
        }
-       
+
        public Object getValueAt(int rowIndex, int columnIndex) {
                CategoriesTableRow partsCategory = elements.get(rowIndex);
-               switch (columnIndex) {
-               case 0:
-                       return partsCategory.getLocalizedCategoryName();
-               case 1:
-                       return Boolean.valueOf(partsCategory.isMultipleSelectable());
-               case 2:
-                       return partsCategory.getVisibleRows();
-               case 3:
+               ColumnDef columnDef = COLUMNS[columnIndex];
+               if (columnDef == ColumnDef.USED_LAYERS) {
+                       // USED_LAYERSカラムは行データの外部からデータを収集して表示する
                        StringBuilder layerNames = new StringBuilder();
                        List<LayersTableRow> layers = null;
                        if (usedCategoryDetector != null) {
@@ -2141,71 +2224,54 @@ class CategoriesTableModel extends AbstractTableModelWithComboBoxModel<Categorie
                                }
                        }
                        return layerNames.toString();
-               default:
-                       return "***";
                }
+
+               // USED_LAYERSカラム以外
+               return columnDef.getValue(partsCategory);
        }
-       
+
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                CategoriesTableRow partsCategory = elements.get(rowIndex);
                try {
-                       switch (columnIndex) {
-                       case 0:
-                               partsCategory.setLocalizedCategoryName((String) aValue);
-                               break;
-                       case 1:
-                               partsCategory.setMultipleSelectable(((Boolean) aValue).booleanValue());
-                               break;
-                       case 2:
-                               partsCategory.setVisibleRows(((Number) aValue).intValue());
-                               break;
-                       default:
-                               return;
+                       ColumnDef columnDef = COLUMNS[columnIndex];
+                       if (columnDef.isEditable()) {
+                               columnDef.setValue(partsCategory, aValue);
+                               fireTableCellUpdated(rowIndex, columnIndex);
                        }
-                       fireTableCellUpdated(rowIndex, columnIndex);
 
                } catch (RuntimeException ex) {
                        logger.log(Level.FINE, "value set failed. (" + rowIndex + ", " + columnIndex + "): " + aValue, ex);
                        // 無視する.
                }
        }
-       
+
        @Override
        public Class<?> getColumnClass(int columnIndex) {
-               if (columnIndex == 1) {
-                       return Boolean.class;
-               }
-               if (columnIndex == 2) {
-                       return Integer.class;
-               }
-               return String.class;
+               return COLUMNS[columnIndex].getDataType();
        }
 
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
-               if (columnIndex >= categoriesColumnName.length - 1) {
-                       return false;
-               }
-               return isEditable();
+               return isEditable() && COLUMNS[columnIndex].isEditable();
        }
 }
 
 /**
  * レイヤーのテーブル編集モデル
- * 
+ *
  * @author seraphy
  */
 class LayersTableModel extends AbstractTableModelWithComboBoxModel<LayersTableRow> {
-       
+
        private static final long serialVersionUID = 1L;
-       
+
        private static final Logger logger = Logger.getLogger(LayersTableModel.class.getName());
 
        private static final String[] layerColumnNames;
-       
+
        private static final int[] layersColumnWidths;
-       
+
        private enum Columns {
                /**
                 * レイヤー名
@@ -2324,7 +2390,7 @@ class LayersTableModel extends AbstractTableModelWithComboBoxModel<LayersTableRo
 
                public abstract boolean setValue(LayersTableRow layer, Object aValue);
        }
-       
+
        static {
                final Properties strings = LocalizedResourcePropertyLoader
                                .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE);
@@ -2414,7 +2480,7 @@ class LayersTableModel extends AbstractTableModelWithComboBoxModel<LayersTableRo
 
                                CategoriesTableRow p1 = o1.getPartsCategory();
                                CategoriesTableRow p2 = o2.getPartsCategory();
-                               
+
                                if (p1 == p2) {
                                        ret = 0;
                                } else if (p1 != null && p2 != null) {
@@ -2463,29 +2529,29 @@ class LayersTableModel extends AbstractTableModelWithComboBoxModel<LayersTableRo
 
 /**
  * レイヤーのテーブル編集モデルで使うレイヤー編集クラス
- * 
+ *
  * @author seraphy
  */
 class LayersTableRow {
 
        private String layerId;
-       
+
        private String layerName;
-       
+
        private CategoriesTableRow partsCategory;
-       
+
        private ColorGroupsTableRow colorGroup = ColorGroupsTableRow.NA;
-       
+
        private int order;
-       
+
        private String dir;
-       
+
        private ColorModels colorModel = ColorModels.DEFAULT;
 
        public LayersTableRow() {
                super();
        }
-       
+
        public String getLayerId() {
                return layerId;
        }
@@ -2544,14 +2610,14 @@ class LayersTableRow {
                        throw new IllegalArgumentException();
                }
                dir = dir.trim();
-               
+
                if (dir.indexOf("/") >= 0 || dir.indexOf("\\") >= 0 || dir.indexOf("..") >= 0 || dir.endsWith(".")) {
                        throw new IllegalArgumentException("not simple name: " + dir);
                }
-               
+
                this.dir = dir;
        }
-       
+
        public ColorModels getColorModel() {
                return colorModel;
        }
@@ -2564,7 +2630,7 @@ class LayersTableRow {
                return layerName != null && layerName.trim().length() > 0
                                && dir != null && dir.trim().length() > 0 && partsCategory != null && colorGroup != null;
        }
-       
+
        public Layer toLayer() {
                if (!isValid()) {
                        return null;
@@ -2584,63 +2650,63 @@ class LayersTableRow {
 
 /**
  * パーツセットのテーブルの行編集モデル
- * 
+ *
  * @author seraphy
  */
 class PresetsTableRow {
-       
+
        private PartsSet partsSet;
-       
+
        public PresetsTableRow(PartsSet partsSet) {
                if (partsSet == null) {
                        throw new IllegalArgumentException();
                }
                this.partsSet = partsSet.clone();
        }
-       
+
        public String getPartsSetId() {
                return partsSet.getPartsSetId();
        }
-       
+
        public String getLocalizedName() {
                return partsSet.getLocalizedName();
        }
-       
+
        public void setLocalizedName(String localizedName) {
                partsSet.setLocalizedName(localizedName);
        }
-       
+
        public boolean isPresetParts() {
                return partsSet.isPresetParts();
        }
-       
+
        public void setPresetParts(boolean checked) {
                partsSet.setPresetParts(checked);
        }
-       
+
        public PartsSet convert() {
                return partsSet.clone();
        }
-       
+
 }
 
 /**
  * パーツセットのテーブル編集モデル
- * 
+ *
  * @author seraphy
  */
 class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTableRow> {
-       
+
        private static final long serialVersionUID = 1L;
-       
+
        private static final Logger logger = Logger.getLogger(PartssetsTableModel.class.getName());
 
        private static final String[] partssetsColumnNames;
-       
+
        private static final int[] partssetsColumnWidths;
-       
+
        private String defaultPartsSetId;
-       
+
        static {
                final Properties strings = LocalizedResourcePropertyLoader
                                .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE);
@@ -2660,31 +2726,31 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
 
        public PartssetsTableModel() {
        }
-       
+
        public void setDefaultPartsSetId(String defaultPartsSetId) {
                this.defaultPartsSetId = defaultPartsSetId;
        }
-       
+
        public String getDefaultPartsSetId() {
                return defaultPartsSetId;
        }
-       
+
        public void adjustColumnModel(TableColumnModel columnModel) {
                for (int idx = 0; idx < partssetsColumnWidths.length; idx++) {
                        columnModel.getColumn(idx).setPreferredWidth(partssetsColumnWidths[idx]);
                }
        }
-       
+
        public int getColumnCount() {
                return partssetsColumnNames.length;
        }
-       
+
        @Override
        public String getColumnName(int column) {
                return partssetsColumnNames[column];
        }
-       
-       
+
+
        public Object getValueAt(int rowIndex, int columnIndex) {
                PresetsTableRow rowModel = elements.get(rowIndex);
                switch (columnIndex) {
@@ -2700,7 +2766,7 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
                        return null;
                }
        }
-       
+
        private String getUsedParts(PresetsTableRow rowModel) {
                StringBuilder buf = new StringBuilder();
                PartsSet partsSet = rowModel.convert();
@@ -2726,7 +2792,7 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
                }
                return buf.toString();
        }
-       
+
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                PresetsTableRow rowModel = elements.get(rowIndex);
@@ -2773,7 +2839,7 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
                        // 無視する.
                }
        }
-       
+
        @Override
        public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
@@ -2789,7 +2855,7 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
                }
                return String.class;
        }
-       
+
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
                switch (columnIndex ) {
@@ -2810,36 +2876,36 @@ class PartssetsTableModel extends AbstractTableModelWithComboBoxModel<PresetsTab
 
 /**
  * お勧めリンクのテーブルの行編集モデル
- * 
+ *
  * @author seraphy
  */
 class RecommendationTableRow {
-       
+
        private RecommendationURL recommendationURL;
-       
+
        public RecommendationTableRow(RecommendationURL recommendationURL) {
                if (recommendationURL == null) {
                        throw new IllegalArgumentException();
                }
                this.recommendationURL = recommendationURL.clone();
        }
-       
+
        public String getLocalizedName() {
                return recommendationURL.getDisplayName();
        }
-       
+
        public void setLocalizedName(String localizedName) {
                recommendationURL.setDisplayName(localizedName);
        }
-       
+
        public String getURL() {
                return recommendationURL.getUrl();
        }
-       
+
        public void setURL(String url) {
                recommendationURL.setUrl(url);
        }
-       
+
        public RecommendationURL convert() {
                return recommendationURL.clone();
        }
@@ -2847,19 +2913,19 @@ class RecommendationTableRow {
 
 /**
  * お勧めリンクのテーブル編集モデル
- * 
+ *
  * @author seraphy
  */
 class RecommendationTableModel extends AbstractTableModelWithComboBoxModel<RecommendationTableRow> {
-       
+
        private static final long serialVersionUID = 1L;
-       
+
        private static final Logger logger = Logger.getLogger(PartssetsTableModel.class.getName());
 
        private static final String[] partssetsColumnNames;
-       
+
        private static final int[] partssetsColumnWidths;
-       
+
        static {
                final Properties strings = LocalizedResourcePropertyLoader
                                .getCachedInstance().getLocalizedProperties(ProfileEditDialog.STRINGS_RESOURCE);
@@ -2875,27 +2941,27 @@ class RecommendationTableModel extends AbstractTableModelWithComboBoxModel<Recom
 
        public RecommendationTableModel() {
        }
-       
+
        public void adjustColumnModel(TableColumnModel columnModel) {
                for (int idx = 0; idx < partssetsColumnWidths.length; idx++) {
                        columnModel.getColumn(idx).setPreferredWidth(partssetsColumnWidths[idx]);
                }
        }
-       
+
        public void addNew() {
                addRow(new RecommendationTableRow(new RecommendationURL()));
        }
-       
+
        public int getColumnCount() {
                return partssetsColumnNames.length;
        }
-       
+
        @Override
        public String getColumnName(int column) {
                return partssetsColumnNames[column];
        }
-       
-       
+
+
        public Object getValueAt(int rowIndex, int columnIndex) {
                RecommendationTableRow rowModel = elements.get(rowIndex);
                switch (columnIndex) {
@@ -2907,7 +2973,7 @@ class RecommendationTableModel extends AbstractTableModelWithComboBoxModel<Recom
                        return null;
                }
        }
-       
+
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
                RecommendationTableRow rowModel = elements.get(rowIndex);
@@ -2933,7 +2999,7 @@ class RecommendationTableModel extends AbstractTableModelWithComboBoxModel<Recom
                        // 無視する.
                }
        }
-       
+
        @Override
        public Class<?> getColumnClass(int columnIndex) {
                switch (columnIndex) {
@@ -2945,7 +3011,7 @@ class RecommendationTableModel extends AbstractTableModelWithComboBoxModel<Recom
                }
                return String.class;
        }
-       
+
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
                switch (columnIndex ) {
index 4ebc355..147c049 100644 (file)
@@ -286,7 +286,7 @@ public class PartsSelectionManager {
         * 選択選択パーツカテゴリの選択解除を許可するか?<br>
         * @return 許可する場合はtrue
         */
-       public boolean isDeselectableSingleCategory() {
+       public boolean isDeselectableAlways() {
                return deselectableAllCategory;
        }
 
@@ -294,21 +294,21 @@ public class PartsSelectionManager {
         * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>
         * @param deselectable 許可する場合はtrue
         */
-       public void setDeselectableSingleCategory(boolean deselectable) {
+       public void setDeselectableAlways(boolean deselectable) {
                this.deselectableAllCategory = deselectable;
                for (ImageSelectPanel imageSelectPanel : this.imageSelectPanels.values()) {
-                       imageSelectPanel.setDeselectableSingleCategory(deselectable);
+                       imageSelectPanel.setDeselectableAlways(deselectable);
                }
        }
 
        /**
         * パーツ選択をすべて解除する.<br>
-        * 単一選択カテゴリが解除されるかどうかは、{@link #isDeselectableSingleCategory()}による.<br>
+        * 単一選択カテゴリが解除されるかどうかは、{@link ImageSelectPanel#isDeselectableAlways()}による.<br>
         */
        public void deselectAll() {
                for (ImageSelectPanel imageSelectPanel : this.imageSelectPanels.values()) {
-                       if (imageSelectPanel.getPartsCategory().isMultipleSelectable()
-                                       || imageSelectPanel.isDeselectableSingleCategory()) {
+                       if (imageSelectPanel.getPartsCategory().isOptional()
+                                       || imageSelectPanel.isDeselectableAlways()) {
                                imageSelectPanel.deselectAll();
                        }
                }
index 47147c4..75da77a 100644 (file)
 
        <entry key="categories.column.categoryname">Category Name</entry>
        <entry key="categories.column.multipleselectable">Multiple selectable</entry>
+       <entry key="categories.column.optional">Optional</entry>
        <entry key="categories.column.displayrowcount">Display Row Count</entry>
        <entry key="categories.column.usedlayers">Used Layers</entry>
        <entry key="categories.column.categoryname.width">100</entry>
        <entry key="categories.column.multipleselectable.width">50</entry>
+       <entry key="categories.column.optional.width">50</entry>
        <entry key="categories.column.displayrowcount.width">50</entry>
        <entry key="categories.column.usedlayers.width">300</entry>
 
index c08558e..2826d85 100644 (file)
 
        <entry key="categories.column.categoryname">カテゴリ名</entry>
        <entry key="categories.column.multipleselectable">複数選択可</entry>
+       <entry key="categories.column.optional">省略可</entry>
        <entry key="categories.column.displayrowcount">表示行数</entry>
        <entry key="categories.column.usedlayers">使用しているレイヤー</entry>
        <entry key="categories.column.categoryname.width">100</entry>
        <entry key="categories.column.multipleselectable.width">50</entry>
+       <entry key="categories.column.optional.width">50</entry>
        <entry key="categories.column.displayrowcount.width">50</entry>
        <entry key="categories.column.usedlayers.width">300</entry>
 
@@ -75,7 +77,7 @@
        <entry key="partssets.column.preset.width">50</entry>
        <entry key="partssets.column.partssetname.width">150</entry>
        <entry key="partssets.column.usedpartsname.width">200</entry>
-       
+
        <entry key="panel.recommendations">お勧めリンク</entry>
        <entry key="recommendations.column.displayName">説明</entry>
        <entry key="recommendations.column.url">URL</entry>
index 6c110bf..b660cc8 100644 (file)
 
        <entry key="categories.column.categoryname">分类名称</entry>
        <entry key="categories.column.multipleselectable">可多选</entry>
+       <entry key="categories.column.optional">可选的</entry>
        <entry key="categories.column.displayrowcount">显示行数</entry>
        <entry key="categories.column.usedlayers">使用的图层</entry>
        <entry key="categories.column.categoryname.width">100</entry>
        <entry key="categories.column.multipleselectable.width">50</entry>
+       <entry key="categories.column.optional.width">50</entry>
        <entry key="categories.column.displayrowcount.width">50</entry>
        <entry key="categories.column.usedlayers.width">300</entry>
 
index 12fc829..69b9740 100644 (file)
        <entry key="edit.information.mnemonicDisp">(I)</entry>
 
        <entry key="edit.deselectall.text">パーツの選択を解除する</entry>
-       <entry key="edit.deselectparts.text">å\8d\98ä¸\80é\81¸æ\8a\9eカテゴリのパーツを解除可能にする</entry>
+       <entry key="edit.deselectparts.text">å\85¨カテゴリのパーツを解除可能にする</entry>
 
        <entry key="edit.enableAutoShrink.text">カテゴリパネルを切替え時に縮小する</entry>
        <entry key="edit.enableAutoShrink.ignoreMacOSX">false</entry>
index cce9767..c90571e 100644 (file)
                       </xs:restriction>
                     </xs:simpleType>
                   </xs:attribute>
+                  <xs:attribute name="optional" form="unqualified" use="optional">
+                    <xs:annotation>
+                      <xs:documentation xml:lang="ja">このカテゴリでパーツが省略可能であるか?</xs:documentation>
+                    </xs:annotation>
+                    <xs:simpleType>
+                      <xs:restriction base="xs:boolean">
+                        <xs:pattern value="true" />
+                        <xs:pattern value="false" />
+                      </xs:restriction>
+                    </xs:simpleType>
+                  </xs:attribute>
                 </xs:complexType>
               </xs:element>
             </xs:sequence>