OSDN Git Service

カスタムレイヤーの既定順序の自動設定と、レイヤー順序の小数点指定対応
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / ui / ImageSelectPanel.java
1 package charactermanaj.ui;\r
2 \r
3 import java.awt.BorderLayout;\r
4 import java.awt.Color;\r
5 import java.awt.Component;\r
6 import java.awt.Dimension;\r
7 import java.awt.Font;\r
8 import java.awt.Insets;\r
9 import java.awt.Rectangle;\r
10 import java.awt.event.ActionEvent;\r
11 import java.awt.event.ActionListener;\r
12 import java.awt.event.FocusAdapter;\r
13 import java.awt.event.FocusEvent;\r
14 import java.awt.event.KeyEvent;\r
15 import java.awt.event.MouseAdapter;\r
16 import java.awt.event.MouseEvent;\r
17 import java.util.ArrayList;\r
18 import java.util.Arrays;\r
19 import java.util.Collection;\r
20 import java.util.Collections;\r
21 import java.util.Comparator;\r
22 import java.util.EventListener;\r
23 import java.util.EventObject;\r
24 import java.util.HashMap;\r
25 import java.util.Iterator;\r
26 import java.util.LinkedList;\r
27 import java.util.List;\r
28 import java.util.Properties;\r
29 \r
30 import javax.swing.AbstractAction;\r
31 import javax.swing.Action;\r
32 import javax.swing.ActionMap;\r
33 import javax.swing.BorderFactory;\r
34 import javax.swing.DefaultListSelectionModel;\r
35 import javax.swing.InputMap;\r
36 import javax.swing.JButton;\r
37 import javax.swing.JLabel;\r
38 import javax.swing.JPanel;\r
39 import javax.swing.JPopupMenu;\r
40 import javax.swing.JScrollPane;\r
41 import javax.swing.JTable;\r
42 import javax.swing.JToolBar;\r
43 import javax.swing.KeyStroke;\r
44 import javax.swing.ListSelectionModel;\r
45 import javax.swing.event.ListSelectionEvent;\r
46 import javax.swing.event.ListSelectionListener;\r
47 import javax.swing.event.TableModelEvent;\r
48 import javax.swing.event.TableModelListener;\r
49 import javax.swing.table.AbstractTableModel;\r
50 import javax.swing.table.DefaultTableColumnModel;\r
51 import javax.swing.table.TableCellRenderer;\r
52 import javax.swing.table.TableColumn;\r
53 \r
54 import charactermanaj.model.AppConfig;\r
55 import charactermanaj.model.PartsCategory;\r
56 import charactermanaj.model.PartsIdentifier;\r
57 import charactermanaj.model.PartsSpec;\r
58 import charactermanaj.model.PartsSpecResolver;\r
59 import charactermanaj.util.LocalizedResourcePropertyLoader;\r
60 import charactermanaj.util.UIHelper;\r
61 \r
62 /**\r
63  * 各パーツの選択パネル(カテゴリ別)\r
64  * @author seraphy\r
65  */\r
66 public class ImageSelectPanel extends JPanel {\r
67 \r
68         private static final long serialVersionUID = 1L;\r
69 \r
70         protected static final String STRINGS_RESOURCE = "languages/imageselectpanel";\r
71 \r
72 \r
73         /**\r
74          * 変更通知を受けるリスナ\r
75          * @author seraphy\r
76          */\r
77         public interface ImageSelectPanelListener extends EventListener {\r
78 \r
79                 /**\r
80                  * 選択が変更された場合\r
81                  * @param event\r
82                  */\r
83                 void onSelectChange(ImageSelectPanelEvent event);\r
84 \r
85                 /**\r
86                  * アイテムが選択された場合\r
87                  * @param event\r
88                  */\r
89                 void onChange(ImageSelectPanelEvent event);\r
90 \r
91                 /**\r
92                  * 色変更ボタンが押された場合\r
93                  * @param event\r
94                  */\r
95                 void onChangeColor(ImageSelectPanelEvent event);\r
96 \r
97                 /**\r
98                  * 設定ボタンが押された場合\r
99                  * @param event\r
100                  */\r
101                 void onPreferences(ImageSelectPanelEvent event);\r
102 \r
103                 /**\r
104                  * タイトルがクリックされた場合\r
105                  * @param event\r
106                  */\r
107                 void onTitleClick(ImageSelectPanelEvent event);\r
108 \r
109                 /**\r
110                  * タイトルがクリックされた場合\r
111                  * @param event\r
112                  */\r
113                 void onTitleDblClick(ImageSelectPanelEvent event);\r
114         };\r
115 \r
116 \r
117         /**\r
118          * 変更通知イベント\r
119          * @author seraphy\r
120          */\r
121         public static class ImageSelectPanelEvent extends EventObject {\r
122 \r
123                 private static final long serialVersionUID = 1L;\r
124 \r
125                 public ImageSelectPanelEvent(ImageSelectPanel src) {\r
126                         super(src);\r
127                 }\r
128 \r
129                 public ImageSelectPanel getImageSelectPanel() {\r
130                         return (ImageSelectPanel) getSource();\r
131                 }\r
132         }\r
133 \r
134         /**\r
135          * 表示モード\r
136          * @author seraphy\r
137          */\r
138         public enum DisplayMode {\r
139                 /**\r
140                  * 最小化モード\r
141                  */\r
142                 MINIMIZED,\r
143 \r
144                 /**\r
145                  * 通常モード\r
146                  */\r
147                 NORMAL,\r
148 \r
149                 /**\r
150                  * 最大サイズフリーモード\r
151                  */\r
152                 EXPANDED\r
153         }\r
154 \r
155         /**\r
156          * パネルノ拡大・縮小時のステップサイズ\r
157          */\r
158         private static final int rowStep = 2;\r
159 \r
160         /**\r
161          * 変更通知を受けるリスナー\r
162          */\r
163         private final LinkedList<ImageSelectPanelListener> listeners = new LinkedList<ImageSelectPanelListener>();\r
164 \r
165         /**\r
166          * リストの一行の高さ\r
167          */\r
168         private final int rowHeight;\r
169 \r
170         /**\r
171          * パネルの最小高さ (ボーダー上限 + ヘッダ行の高さ)\r
172          */\r
173         private final int minHeight;\r
174 \r
175         /**\r
176          * 現在の表示行数\r
177          */\r
178         private int numOfVisibleRows;\r
179 \r
180         /**\r
181          * 最小化モードであるか?\r
182          */\r
183         private DisplayMode displayMode;\r
184 \r
185         /**\r
186          * パーツ情報ソース\r
187          */\r
188         private PartsSpecResolver partsSpecResolver;\r
189 \r
190         /**\r
191          * パーツ選択テーブル\r
192          */\r
193         private final JTable partsSelectTable;\r
194 \r
195         /**\r
196          * パーツ選択テーブルモデル\r
197          */\r
198         private final PartsSelectListModel partsSelectTableModel;\r
199 \r
200         /**\r
201          * 選択中のアイテム(複数選択の場合はフォーカスされているもの)、もしくはnull\r
202          */\r
203         private PartsIdentifier selectedPartsIdentifier;\r
204 \r
205         /**\r
206          * 選択中のアイテムのリスト(順序あり)、もしくは空\r
207          */\r
208         private List<PartsIdentifier> selectedPartsIdentifiers = Collections.emptyList();\r
209 \r
210         /**\r
211          * このパネルが対象とするカテゴリ情報\r
212          */\r
213         private final PartsCategory partsCategory;\r
214 \r
215 \r
216         /**\r
217          * イメージ選択パネルを構築する\r
218          * @param partsCategory パーツカテゴリ\r
219          * @param partsSpecResolver キャラクターデータ\r
220          */\r
221         public ImageSelectPanel(final PartsCategory partsCategory, final PartsSpecResolver partsSpecResolver) {\r
222                 if (partsCategory == null || partsSpecResolver == null) {\r
223                         throw new IllegalArgumentException();\r
224                 }\r
225                 this.partsCategory = partsCategory;\r
226                 this.partsSpecResolver = partsSpecResolver;\r
227 \r
228                 setLayout(new BorderLayout());\r
229 \r
230                 setBorder(BorderFactory.createCompoundBorder(\r
231                                 BorderFactory.createEmptyBorder(3, 3, 3, 3),\r
232                                 BorderFactory.createCompoundBorder(\r
233                                                 BorderFactory.createEtchedBorder(),\r
234                                                 BorderFactory.createEmptyBorder(3, 3, 3, 3))\r
235                                         )\r
236                                 );\r
237 \r
238                 partsSelectTableModel = new PartsSelectListModel(partsCategory);\r
239 \r
240                 final DefaultTableColumnModel columnModel = new DefaultTableColumnModel();\r
241                 TableColumn checkColumn = new TableColumn(0, 32);\r
242                 checkColumn.setMaxWidth(42);\r
243                 columnModel.addColumn(checkColumn);\r
244                 columnModel.addColumn(new TableColumn(1, 100));\r
245 \r
246                 final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();\r
247                 selectionModel.addListSelectionListener(new ListSelectionListener() {\r
248                         public void valueChanged(ListSelectionEvent e) {\r
249                                 if (!e.getValueIsAdjusting()) {\r
250                                         onSelectChange(new ImageSelectPanelEvent(ImageSelectPanel.this));\r
251                                 }\r
252                         }\r
253                 });\r
254 \r
255                 AppConfig appConfig = AppConfig.getInstance();\r
256 \r
257                 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
258                                 .getLocalizedProperties(STRINGS_RESOURCE);\r
259 \r
260                 final Color selectedItemColor = appConfig.getCheckedItemBgColor();\r
261 \r
262                 partsSelectTable = new JTable(partsSelectTableModel, columnModel, selectionModel) {\r
263                         private static final long serialVersionUID = 1L;\r
264                         @Override\r
265                         public Component prepareRenderer(TableCellRenderer renderer,\r
266                                         int row, int column) {\r
267                                 Component comp = super.prepareRenderer(renderer, row, column);\r
268                                 if (isCellSelected(row, column) && hasFocus()) {\r
269                                         // フォーカスのあるセル選択の背景色\r
270                                         comp.setBackground(getSelectionBackground());\r
271                                 } else {\r
272                                         // フォーカスのないセル選択行\r
273                                         Boolean chk = (Boolean) getModel().getValueAt(row, 0);\r
274                                         comp.setForeground(getForeground());\r
275                                         if (chk.booleanValue()) {\r
276                                                 // チェック済みの場合の背景色\r
277                                                 comp.setBackground(selectedItemColor);\r
278                                         } else {\r
279                                                 // 通常の背景色\r
280                                                 comp.setBackground(getBackground());\r
281                                         }\r
282                                 }\r
283                                 return comp;\r
284                         }\r
285                         @Override\r
286                         public String getToolTipText(MouseEvent event) {\r
287                                 // マウスが置かれている行のツールチップとしてパーツ名を表示する.\r
288                                 int row = rowAtPoint(event.getPoint());\r
289                                 int mx = partsSelectTableModel.getRowCount();\r
290                                 if (row >= 0 && row < mx) {\r
291                                         PartsSelectRow rowModel = partsSelectTableModel.getRow(row);\r
292                                         PartsIdentifier partsIdentifier = rowModel.getPartsIdentifier();\r
293                                         PartsSpec partsSpec = partsSpecResolver.getPartsSpec(partsIdentifier);\r
294                                         String suffix = "";\r
295                                         if (partsSpec != null) {\r
296                                                 // パーツの作者名とバージョンがあれば、それを末尾につけて表示する.\r
297                                                 String author = partsSpec.getAuthor();\r
298                                                 double version = partsSpec.getVersion();\r
299                                                 if (author != null) {\r
300                                                         if (version > 0) {\r
301                                                                 suffix = " (" + author + " " + version + ")";\r
302                                                         } else {\r
303                                                                 suffix = " (" + author + ")";\r
304                                                         }\r
305                                                 }\r
306                                         }\r
307                                         return partsIdentifier.getLocalizedPartsName() + suffix;\r
308                                 }\r
309                                 return null;\r
310                         }\r
311                 };\r
312                 partsSelectTable.addFocusListener(new FocusAdapter() {\r
313                         @Override\r
314                         public void focusGained(FocusEvent e) {\r
315                                 partsSelectTable.repaint();\r
316                         }\r
317                         @Override\r
318                         public void focusLost(FocusEvent e) {\r
319                                 partsSelectTable.repaint();\r
320                         }\r
321                 });\r
322                 final JPopupMenu partsSelectTablePopupMenu = new JPopupMenu();\r
323                 Action actDeselectAll = new AbstractAction(\r
324                                 strings.getProperty("popupmenu.deselectall")) {\r
325                         private static final long serialVersionUID = 9132032971228670868L;\r
326                         public void actionPerformed(ActionEvent e) {\r
327                                 deselectAll();\r
328                         }\r
329                 };\r
330                 partsSelectTablePopupMenu.add(actDeselectAll);\r
331 \r
332                 partsSelectTable.addMouseListener(new MouseAdapter() {\r
333                         @Override\r
334                         public void mousePressed(MouseEvent e) {\r
335                                 evaluatePopup(e);\r
336                         }\r
337                         @Override\r
338                         public void mouseReleased(MouseEvent e) {\r
339                                 evaluatePopup(e);\r
340                         }\r
341                         private void evaluatePopup(MouseEvent e) {\r
342                                 if ((partsCategory.isMultipleSelectable() || isDeselectableSingleCategory())\r
343                                                 && e.isPopupTrigger()) {\r
344                                         partsSelectTablePopupMenu.show(partsSelectTable, e.getX(), e.getY());\r
345                                 }\r
346                         }\r
347                 });\r
348 \r
349                 partsSelectTableModel.addTableModelListener(new TableModelListener() {\r
350                         public void tableChanged(TableModelEvent e) {\r
351                                 if (e.getType() == TableModelEvent.UPDATE) {\r
352                                         onChange(new ImageSelectPanelEvent(ImageSelectPanel.this));\r
353                                 }\r
354                         }\r
355                 });\r
356                 partsSelectTable.setSelectionBackground(appConfig.getSelectedItemBgColor());\r
357                 if (partsCategory.isMultipleSelectable()) {\r
358                         partsSelectTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
359                 } else {\r
360                         partsSelectTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
361                 }\r
362                 partsSelectTable.setRowSelectionAllowed(true);\r
363                 partsSelectTable.setTableHeader(null);\r
364                 partsSelectTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);\r
365                 partsSelectTable.setShowVerticalLines(false);\r
366                 partsSelectTable.setShowHorizontalLines(false);\r
367 \r
368         InputMap im = partsSelectTable.getInputMap();\r
369                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "toggleCheck");\r
370                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "resetCheck");\r
371                 ActionMap am = partsSelectTable.getActionMap();\r
372                 am.put("toggleCheck", new AbstractAction() {\r
373                         private static final long serialVersionUID = 1L;\r
374                         public void actionPerformed(ActionEvent e) {\r
375                                 int[] selectedRows = partsSelectTable.getSelectedRows();\r
376                                 boolean[] checks = partsSelectTableModel.getChecks(selectedRows);\r
377                                 int checkedCount = 0;\r
378                                 for (boolean checked : checks) {\r
379                                         if (checked) {\r
380                                                 checkedCount++;\r
381                                         }\r
382                                 }\r
383                                 if (checks.length == checkedCount) {\r
384                                         // 選択しているアイテムのすべてがチェック済みである\r
385                                         partsSelectTableModel.setChecks(false, selectedRows);\r
386                                 } else {\r
387                                         // 選択しているアイテムの一部もしくは全部がチェックされていない\r
388                                         partsSelectTableModel.setChecks(true, selectedRows);\r
389                                 }\r
390                         }\r
391                 });\r
392                 am.put("resetCheck", new AbstractAction() {\r
393                         private static final long serialVersionUID = 1L;\r
394                         public void actionPerformed(ActionEvent e) {\r
395                                 partsSelectTableModel.setChecks(false, partsSelectTable.getSelectedRows());\r
396                         }\r
397                 });\r
398 \r
399                 JScrollPane scrollPane = new JScrollPane(partsSelectTable);\r
400                 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);\r
401 \r
402                 UIHelper uiUtl = UIHelper.getInstance();\r
403                 JButton leftBtn = uiUtl.createTransparentButton("icons/left.png", "icons/left2.png");\r
404                 JButton rightBtn = uiUtl.createTransparentButton("icons/right.png", "icons/right2.png");\r
405                 JButton colorBtn = uiUtl.createTransparentButton("icons/color.png", "icons/color2.png");\r
406                 JButton configBtn = uiUtl.createTransparentButton("icons/config.png", "icons/config2.png");\r
407 \r
408                 leftBtn.setToolTipText(strings.getProperty("tooltip.shrink"));\r
409                 rightBtn.setToolTipText(strings.getProperty("tooltip.expand"));\r
410                 colorBtn.setToolTipText(strings.getProperty("tooltip.color"));\r
411                 configBtn.setToolTipText(strings.getProperty("tooltip.config"));\r
412 \r
413                 leftBtn.addActionListener(new ActionListener() {\r
414                         public void actionPerformed(ActionEvent e) {\r
415                                 if (isMinimizeMode()) {\r
416                                         setMinimizeMode(false);\r
417                                 } else {\r
418                                         shrink();\r
419                                 }\r
420                         }\r
421                 });\r
422                 rightBtn.addActionListener(new ActionListener() {\r
423                         public void actionPerformed(ActionEvent e) {\r
424                                 if (isMinimizeMode()) {\r
425                                         setMinimizeMode(false);\r
426                                 } else {\r
427                                         expand();\r
428                                 }\r
429                         }\r
430                 });\r
431                 colorBtn.addActionListener(new ActionListener() {\r
432                         public void actionPerformed(ActionEvent e) {\r
433                                 onChangeColor();\r
434                         }\r
435                 });\r
436                 configBtn.addActionListener(new ActionListener() {\r
437                         public void actionPerformed(ActionEvent e) {\r
438                                 onPreferences();\r
439                         }\r
440                 });\r
441 \r
442 \r
443                 JPanel btnPanelGrp = new JPanel(new BorderLayout());\r
444 \r
445                 JToolBar toolBar = new JToolBar();\r
446                 toolBar.setFloatable(false);\r
447                 toolBar.add(leftBtn);\r
448                 toolBar.add(rightBtn);\r
449                 toolBar.add(colorBtn);\r
450                 //toolBar.add(configBtn); // 設定ボタン (現在は非表示)\r
451 \r
452                 btnPanelGrp.add(toolBar, BorderLayout.NORTH);\r
453 \r
454                 if (partsCategory.isMultipleSelectable()) {\r
455                         UIHelper uiUty = UIHelper.getInstance();\r
456                         JButton upBtn = uiUty.createTransparentButton("icons/arrow_up.png", "icons/arrow_up2.png");\r
457                         JButton downBtn = uiUty.createTransparentButton("icons/arrow_down.png", "icons/arrow_down2.png");\r
458                         JButton sortBtn = uiUty.createTransparentButton("icons/sort.png", "icons/sort2.png");\r
459 \r
460                         upBtn.setToolTipText(strings.getProperty("tooltip.up"));\r
461                         downBtn.setToolTipText(strings.getProperty("tooltip.down"));\r
462                         sortBtn.setToolTipText(strings.getProperty("tooltip.sort"));\r
463 \r
464                         upBtn.addActionListener(new ActionListener() {\r
465                                 public void actionPerformed(ActionEvent e) {\r
466                                         onUp();\r
467                                 }\r
468                         });\r
469                         downBtn.addActionListener(new ActionListener() {\r
470                                 public void actionPerformed(ActionEvent e) {\r
471                                         onDown();\r
472                                 }\r
473                         });\r
474                         sortBtn.addActionListener(new ActionListener() {\r
475                                 public void actionPerformed(ActionEvent e) {\r
476                                         onSort();\r
477                                 }\r
478                         });\r
479 \r
480                         JToolBar toolBar2 = new JToolBar();\r
481                         toolBar2.setFloatable(false);\r
482                         toolBar2.add(upBtn);\r
483                         toolBar2.add(downBtn);\r
484                         toolBar2.add(sortBtn);\r
485                         btnPanelGrp.add(toolBar2, BorderLayout.SOUTH);\r
486                 }\r
487 \r
488                 JPanel header = new JPanel(new BorderLayout());\r
489                 header.add(btnPanelGrp, BorderLayout.EAST);\r
490                 final JLabel title = new JLabel(" " + partsCategory.getLocalizedCategoryName() + " ");\r
491                 Font font = title.getFont();\r
492                 title.setFont(font.deriveFont(Font.BOLD));\r
493                 final Color defaultTitleColor = title.getForeground();\r
494                 final Color hilightColor = appConfig.getSelectPanelTitleColor();\r
495 \r
496                 title.addMouseListener(new MouseAdapter() {\r
497                         @Override\r
498                         public void mousePressed(MouseEvent e) {\r
499                                 if (e.getClickCount() == 2) {\r
500                                         // 正確に2回\r
501                                         onTitleDblClick();\r
502                                 } else if (e.getClickCount() == 1) {\r
503                                         // 正確に1回\r
504                                         onTitleClick();\r
505                                 }\r
506                         }\r
507                         @Override\r
508                         public void mouseEntered(MouseEvent e) {\r
509                                 title.setForeground(hilightColor);\r
510                         }\r
511                         @Override\r
512                         public void mouseExited(MouseEvent e) {\r
513                                 title.setForeground(defaultTitleColor);\r
514                         }\r
515                 });\r
516 \r
517                 header.add(title, BorderLayout.CENTER);\r
518 \r
519                 add(header, BorderLayout.NORTH);\r
520                 add(scrollPane, BorderLayout.CENTER);\r
521 \r
522                 rowHeight = partsSelectTable.getRowHeight();\r
523 \r
524                 // パネルの最小高さ (ボーダー上下 + ヘッダ行高さ)\r
525                 Insets insets = getInsets();\r
526                 minHeight = header.getPreferredSize().height + insets.top + insets.bottom;\r
527 \r
528                 // デフォルトのパネル幅を設定する.\r
529                 Dimension dim = new Dimension(200, 200);\r
530                 setPreferredSize(dim);\r
531 \r
532                 // パネルの初期サイズ\r
533                 numOfVisibleRows = partsCategory.getVisibleRows();\r
534                 setDisplayMode(DisplayMode.NORMAL);\r
535         }\r
536 \r
537         /**\r
538          * 表示行数から推奨のパネル高さを求める.<br>\r
539          * パネル高さは1行の高さ x 表示行数 + ヘッダ + ボーダー上下である.<br>\r
540          * @param numOfVisibleRows 表示行数\r
541          * @return 推奨のパネル高さ\r
542          */\r
543         protected int calcPreferredHeight(int numOfVisibleRows) {\r
544                 return minHeight + Math.max(0, rowHeight * numOfVisibleRows);\r
545         }\r
546 \r
547         /**\r
548          * パーツをパネルにロードします.<br>\r
549          * 既存の内容はリセットされたのち、現在の選択パーツ位置にスクロールします.<br>\r
550          */\r
551         public void loadParts() {\r
552                 partsSelectTableModel.load(partsSpecResolver.getPartsSpecMap(partsCategory).keySet());\r
553                 scrollToSelectedRow();\r
554         }\r
555 \r
556         /**\r
557          * このイメージ選択パネルの該当カテゴリを返します.<br>\r
558          * @return カテゴリ\r
559          */\r
560         public PartsCategory getPartsCategory() {\r
561                 return partsCategory;\r
562         }\r
563 \r
564         /**\r
565          * 現在選択している、すべてのパーツの選択を解除します.<br>\r
566          * 単一選択カテゴリであるかどうかを問わず、常にすべて解除されます.<br>\r
567          * 変更イベントが発生します.<br>\r
568          */\r
569         public void deselectAll() {\r
570                 PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel();\r
571                 ArrayList<PartsSelectRow> rowModels = rowModelList.getRowModelList();\r
572 \r
573                 // すべての選択を解除する.\r
574                 for (PartsSelectRow rowModel : rowModels) {\r
575                         rowModel.setChecked(false);\r
576                 }\r
577 \r
578                 // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません.\r
579                 // そのため再描画させる必要があります.\r
580                 partsSelectTable.repaint();\r
581 \r
582                 // アイテムの選択が変更されたことを通知する.\r
583                 onChange(new ImageSelectPanelEvent(ImageSelectPanel.this));\r
584         }\r
585 \r
586         /**\r
587          * カテゴリのリストでパーツを選択しなおします.<br>\r
588          * 変更イベントは発生しません.<br>\r
589          * @param partsIdentifiers\r
590          */\r
591         public void selectParts(Collection<PartsIdentifier> partsIdentifiers) {\r
592                 if (partsIdentifiers == null) {\r
593                         partsIdentifiers = Collections.emptyList();\r
594                 }\r
595                 PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel();\r
596                 ArrayList<PartsSelectRow> rowModels = rowModelList.getRowModelList();\r
597 \r
598                 for (PartsSelectRow rowModel : rowModels) {\r
599                         rowModel.setChecked(false);\r
600                 }\r
601 \r
602                 ArrayList<PartsIdentifier> partsIdentifiersBuf = new ArrayList<PartsIdentifier>(partsIdentifiers);\r
603                 Collections.reverse(partsIdentifiersBuf);\r
604 \r
605                 for (PartsIdentifier partsIdentifier : partsIdentifiersBuf) {\r
606                         Iterator<PartsSelectRow> ite = rowModels.iterator();\r
607                         while (ite.hasNext()) {\r
608                                 PartsSelectRow rowModel = ite.next();\r
609                                 if (rowModel.getPartsIdentifier().equals(partsIdentifier)) {\r
610                                         rowModel.setChecked(true);\r
611                                         if (partsIdentifiersBuf.size() >= 2 && partsCategory.isMultipleSelectable()) {\r
612                                                 ite.remove();\r
613                                                 rowModels.add(0, rowModel);\r
614                                         }\r
615                                         break;\r
616                                 }\r
617                         }\r
618                 }\r
619 \r
620                 // 選択を保存する\r
621                 selectedPartsIdentifier = getSelectedPartsIdentifier();\r
622                 selectedPartsIdentifiers = getSelectedPartsIdentifiers();\r
623 \r
624                 // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません.\r
625                 // そのため再描画させる必要があります.\r
626                 partsSelectTable.repaint();\r
627 \r
628                 // あたらしく選択されたアイテムが表示されるようにスクロールします.\r
629                 scrollToSelectedRow();\r
630         }\r
631 \r
632         /**\r
633          * カテゴリのリストで選択中のアイテムが見えるようにスクロールする.\r
634          */\r
635         public void scrollToSelectedRow() {\r
636                 PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel();\r
637                 ArrayList<PartsSelectRow> rowModels = rowModelList.getRowModelList();\r
638                 int mx = rowModels.size();\r
639                 for (int row = 0; row < mx; row++) {\r
640                         if (rowModels.get(row).isChecked()) {\r
641                                 Rectangle rct = partsSelectTable.getCellRect(row, 0, true);\r
642                                 partsSelectTable.scrollRectToVisible(rct);\r
643                                 break;\r
644                         }\r
645                 }\r
646         }\r
647 \r
648         /**\r
649          * カテゴリのパネルを最小表示.<br>\r
650          * 最小化の場合は、高さは表示行数ゼロとなりタイトルとボーダーだけとなる.<br>\r
651          * 最小化解除した場合は、標準高さは既定、最大サイズはフリーとなる.<br>\r
652          * @param shrinkMode 最小化モードならばtrue、フリーモードならばfalse\r
653          */\r
654         public void setMinimizeMode(boolean minimizeMode) {\r
655                 setDisplayMode(minimizeMode ? DisplayMode.MINIMIZED : DisplayMode.EXPANDED);\r
656         }\r
657 \r
658         /**\r
659          * 表示モードを切り替えパネルサイズを調整する.<br>\r
660          * @param displayMode 表示モード\r
661          */\r
662         public void setDisplayMode(DisplayMode displayMode) {\r
663                 if (displayMode == null) {\r
664                         displayMode = DisplayMode.NORMAL;\r
665                 }\r
666 \r
667                 Dimension siz = getPreferredSize();\r
668                 Dimension sizMax = getMaximumSize();\r
669 \r
670                 if (displayMode == DisplayMode.MINIMIZED) {\r
671                         int preferredHeight = calcPreferredHeight(0);\r
672                         siz.height = preferredHeight;\r
673                         sizMax.height = preferredHeight;\r
674 \r
675                 } else if (displayMode == DisplayMode.EXPANDED) {\r
676                         int preferredHeight = calcPreferredHeight(numOfVisibleRows);\r
677                         siz.height = preferredHeight;\r
678                         sizMax.height = Integer.MAX_VALUE;\r
679 \r
680                 } else {\r
681                         // DisplayMode.NORMALの場合\r
682                         int preferredHeight = calcPreferredHeight(numOfVisibleRows);\r
683                         siz.height = preferredHeight;\r
684                         sizMax.height = preferredHeight;\r
685                 }\r
686 \r
687                 setPreferredSize(siz);\r
688                 setMinimumSize(siz);\r
689                 setMaximumSize(sizMax);\r
690 \r
691                 this.displayMode = displayMode;\r
692                 revalidate();\r
693         }\r
694 \r
695         public DisplayMode getDisplayMode() {\r
696                 return displayMode;\r
697         }\r
698 \r
699         public boolean isMinimizeMode() {\r
700                 return displayMode == DisplayMode.MINIMIZED;\r
701         }\r
702 \r
703         /**\r
704          * カテゴリのパネルを縮小する.<br>\r
705          * ただし、ヘッダ部よりは小さくならない.<br>\r
706          * 現在の表示モードが標準でなければ縮小せず標準に戻す.<br>\r
707          */\r
708         public void shrink() {\r
709                 if (displayMode == DisplayMode.NORMAL) {\r
710                         // 表示行数を減ずる\r
711                         numOfVisibleRows = Math.max(0, numOfVisibleRows - rowStep);\r
712                 }\r
713                 // 通常モードの適用\r
714                 setDisplayMode(DisplayMode.NORMAL);\r
715         }\r
716 \r
717         /**\r
718          * カテゴリのパネルを拡大する.<br>\r
719          * 現在の表示モードが標準でなければ拡大前せず標準に戻す.<br>\r
720          */\r
721         public void expand() {\r
722                 if (displayMode == DisplayMode.NORMAL) {\r
723                         // 表示行数を加算する\r
724                         numOfVisibleRows += Math.max(0, rowStep);\r
725                 }\r
726                 // 通常モードの適用\r
727                 setDisplayMode(DisplayMode.NORMAL);\r
728         }\r
729 \r
730         public void addImageSelectListener(ImageSelectPanelListener listener) {\r
731                 if (listener == null) {\r
732                         throw new IllegalArgumentException();\r
733                 }\r
734                 listeners.add(listener);\r
735         }\r
736 \r
737         public void removeImageSelectListener(ImageSelectPanelListener listener) {\r
738                 listeners.remove(listener);\r
739         }\r
740 \r
741         public void requestListFocus() {\r
742                 partsSelectTable.requestFocus();\r
743         }\r
744 \r
745         /**\r
746          * 指定したパーツ識別子にフォーカスを当てます.<br>\r
747          * 必要に応じてスクロールされます.<br>\r
748          * 該当するパーツ識別子がなければ何もしません.<br>\r
749          * @param partsIdentifier パーツ識別子\r
750          */\r
751         public void setSelection(PartsIdentifier partsIdentifier) {\r
752                 if (partsIdentifier == null) {\r
753                         return;\r
754                 }\r
755                 PartsCategory partsCategory = partsIdentifier.getPartsCategory();\r
756                 if (!this.partsCategory.equals(partsCategory)) {\r
757                         return;\r
758                 }\r
759 \r
760                 ArrayList<PartsSelectRow> rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList();\r
761                 int mx = rowModelList.size();\r
762                 for (int idx = 0; idx < mx; idx++) {\r
763                         PartsSelectRow partsSelectRow = rowModelList.get(idx);\r
764                         if (partsSelectRow.getPartsIdentifier().equals(partsIdentifier)) {\r
765                                 partsSelectTable.getSelectionModel().setSelectionInterval(idx, idx);\r
766                                 Rectangle rct = partsSelectTable.getCellRect(idx, 0, true);\r
767                                 partsSelectTable.scrollRectToVisible(rct);\r
768                                 partsSelectTable.requestFocus();\r
769                                 return;\r
770                         }\r
771                 }\r
772         }\r
773 \r
774         /**\r
775          * フォーカスのあるアイテムを1つ上に移動します.\r
776          */\r
777         protected void onUp() {\r
778                 int selRow = partsSelectTable.getSelectedRow();\r
779                 if (selRow < 0) {\r
780                         return;\r
781                 }\r
782                 if (selRow > 0) {\r
783                         ArrayList<PartsSelectRow> rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList();\r
784                         PartsSelectRow rowModel = rowModelList.get(selRow);\r
785                         rowModelList.remove(selRow);\r
786                         rowModelList.add(selRow - 1, rowModel);\r
787                         partsSelectTable.setRowSelectionInterval(selRow - 1, selRow - 1);\r
788                         Rectangle rct = partsSelectTable.getCellRect(selRow - 1, 0, true);\r
789                         partsSelectTable.scrollRectToVisible(rct);\r
790                         onChange(new ImageSelectPanelEvent(this));\r
791                 }\r
792                 partsSelectTable.repaint();\r
793                 partsSelectTable.requestFocus();\r
794         }\r
795 \r
796         /**\r
797          * フォーカスのあるアイテムを1つ下に移動します.\r
798          */\r
799         protected void onDown() {\r
800                 int selRow = partsSelectTable.getSelectedRow();\r
801                 if (selRow < 0) {\r
802                         return;\r
803                 }\r
804                 int mx = partsSelectTable.getRowCount();\r
805                 if (selRow < mx - 1) {\r
806                         ArrayList<PartsSelectRow> rowModelList = ((PartsSelectListModel) partsSelectTable.getModel()).getRowModelList();\r
807                         PartsSelectRow rowModel = rowModelList.get(selRow);\r
808                         rowModelList.remove(selRow);\r
809                         rowModelList.add(selRow + 1, rowModel);\r
810                         partsSelectTable.setRowSelectionInterval(selRow + 1, selRow + 1);\r
811                         Rectangle rct = partsSelectTable.getCellRect(selRow + 1, 0, true);\r
812                         partsSelectTable.scrollRectToVisible(rct);\r
813                         onChange(new ImageSelectPanelEvent(this));\r
814                 }\r
815                 partsSelectTable.repaint();\r
816                 partsSelectTable.requestFocus();\r
817         }\r
818 \r
819         /**\r
820          * 選択中のアイテムを選択順序を維持したまま上側に、それ以外は名前順で下側に集めるようにソートします.<br>\r
821          */\r
822         protected void onSort() {\r
823                 if (partsSelectTable.getRowCount() > 0) {\r
824                         partsSelectTableModel.sort();\r
825                         partsSelectTable.setRowSelectionInterval(0, 0);\r
826                         Rectangle rct = partsSelectTable.getCellRect(0, 0, true);\r
827                         partsSelectTable.scrollRectToVisible(rct);\r
828                         partsSelectTable.repaint();\r
829                 }\r
830                 partsSelectTable.requestFocus();\r
831         }\r
832 \r
833         /**\r
834          * タイトルがクリックされた場合\r
835          */\r
836         protected void onTitleClick() {\r
837                 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);\r
838                 for (ImageSelectPanelListener listener : listeners) {\r
839                         listener.onTitleClick(event);\r
840                 }\r
841         }\r
842 \r
843         /**\r
844          * タイトルがダブルクリックされた場合\r
845          */\r
846         protected void onTitleDblClick() {\r
847                 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);\r
848                 for (ImageSelectPanelListener listener : listeners) {\r
849                         listener.onTitleDblClick(event);\r
850                 }\r
851         }\r
852 \r
853         /**\r
854          * カラー変更ボタンが押下された場合\r
855          * @param event\r
856          */\r
857         protected void onChangeColor() {\r
858                 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);\r
859                 for (ImageSelectPanelListener listener : listeners) {\r
860                         listener.onChangeColor(event);\r
861                 }\r
862         }\r
863 \r
864         /**\r
865          * 設定ボタンが押下された場合\r
866          * @param event\r
867          */\r
868         protected void onPreferences() {\r
869                 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);\r
870                 for (ImageSelectPanelListener listener : listeners) {\r
871                         listener.onPreferences(event);\r
872                 }\r
873         }\r
874 \r
875         /**\r
876          * アイテムのチェック状態が変更された場合.\r
877          * @param event\r
878          */\r
879         protected void onChange(ImageSelectPanelEvent event) {\r
880                 List<PartsIdentifier> selectedNews = getSelectedPartsIdentifiers();\r
881                 if (!selectedNews.equals(selectedPartsIdentifiers)) {\r
882                         selectedPartsIdentifiers = selectedNews;\r
883                         for (ImageSelectPanelListener listener : listeners) {\r
884                                 listener.onChange(event);\r
885                         }\r
886                         onSelectChange(event);\r
887                 }\r
888         }\r
889 \r
890         /**\r
891          * アイテムの選択(フォーカス)が変更された場合.\r
892          * @param event\r
893          */\r
894         protected void onSelectChange(ImageSelectPanelEvent event) {\r
895                 PartsIdentifier selectedNew = getSelectedPartsIdentifier();\r
896                 if (!PartsIdentifier.equals(selectedNew, selectedPartsIdentifier)) {\r
897                         selectedPartsIdentifier = selectedNew;\r
898                         for (ImageSelectPanelListener listener : listeners) {\r
899                                 listener.onSelectChange(event);\r
900                         }\r
901                 }\r
902         }\r
903 \r
904         /**\r
905          * 使用中のアイテムの一覧を返す.(選択順)<br>\r
906          * @return 使用中のアイテムの一覧.(選択順)、ひとつもなければ空\r
907          */\r
908         public List<PartsIdentifier> getSelectedPartsIdentifiers() {\r
909                 return partsSelectTableModel.getSelectedPartsIdentifiers();\r
910         }\r
911 \r
912         /**\r
913          * 使用中のアイテムを返す.<br>\r
914          * 複数選択可能である場合は、使用中のアイテムでフォーカスがある最初のアイテムを返す.<br>\r
915          * 単一選択の場合は、最初の使用中アイテムを返す.<br>\r
916          * 複数選択可能で、使用中のアイテムにひとつもフォーカスがあたってない場合は、\r
917          * 最初の使用中アイテムを返す.<br>\r
918          * 使用中アイテムがなければnullを返す.\r
919          * @return 使用中アイテム、もしくはnull\r
920          */\r
921         public PartsIdentifier getSelectedPartsIdentifier() {\r
922 \r
923                 // フォーカスがあたっていて、且つ、チェック状態のアイテムを上から順に走査し、\r
924                 // 該当があれば、最初のものを返す.\r
925                 int[] selRows = partsSelectTable.getSelectedRows();\r
926                 Arrays.sort(selRows);\r
927                 for (int selRow : selRows) {\r
928                         PartsSelectRow row = partsSelectTableModel.getRow(selRow);\r
929                         if (row.isChecked()) {\r
930                                 return row.getPartsIdentifier();\r
931                         }\r
932                 }\r
933 \r
934                 // チェック状態のアイテムの最初のものを返す.\r
935                 List<PartsIdentifier> checkedRows = getSelectedPartsIdentifiers();\r
936                 if (checkedRows.size() > 0) {\r
937                         return checkedRows.get(0);\r
938                 }\r
939 \r
940                 // 該当なし\r
941                 return null;\r
942         }\r
943 \r
944         /**\r
945          * 選択選択パーツカテゴリの選択解除を許可するか?<br>\r
946          * @return 許可する場合はtrue\r
947          */\r
948         public boolean isDeselectableSingleCategory() {\r
949                 return partsSelectTableModel.isDeselectableSingleCategory();\r
950         }\r
951 \r
952         /**\r
953          * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>\r
954          * @param deselectable 許可する場合はtrue\r
955          */\r
956         public void setDeselectableSingleCategory(boolean deselectable) {\r
957                 partsSelectTableModel.setDeselectableSingleCategory(deselectable);\r
958         }\r
959 }\r
960 \r
961 \r
962 /**\r
963  * リストの行モデル.<br>\r
964  * パーツデータ、表示名と使用中フラグを管理する.\r
965  * @author seraphy\r
966  */\r
967 final class PartsSelectRow implements Comparable<PartsSelectRow> {\r
968 \r
969         private PartsIdentifier partsIdentifier;\r
970 \r
971         private boolean checked;\r
972 \r
973         private int displayOrder;\r
974 \r
975         public PartsSelectRow(final PartsIdentifier partsIdentifier, final boolean checked) {\r
976                 this.partsIdentifier = partsIdentifier;\r
977                 this.checked = checked;\r
978         }\r
979 \r
980         /**\r
981          * 選択されているものを上、そうでないものを下に振り分ける。\r
982          * 選択されているもの同士、選択されていないもの同士は、互いのディスプレイ順でソートされる.<br>\r
983          * 選択されているもの同士、選択されていないもの同士で、且つ、同一のディスプレイ順序であればパーツの表示名順でソートされる.<br>\r
984          * @param o 対象\r
985          * @return 比較結果\r
986          */\r
987         public int compareTo(PartsSelectRow o) {\r
988                 int ret = (checked == o.checked) ? 0 : (checked ? -1 : 1);\r
989                 if (ret == 0 && checked) {\r
990                         ret = displayOrder - o.displayOrder;\r
991                 }\r
992                 if (ret == 0) {\r
993                         ret = partsIdentifier.compareTo(o.partsIdentifier);\r
994                 }\r
995                 return ret;\r
996         }\r
997 \r
998         public void setDisplayOrder(int displayOrder) {\r
999                 this.displayOrder = displayOrder;\r
1000         }\r
1001 \r
1002         public int getDisplayOrder() {\r
1003                 return this.displayOrder;\r
1004         }\r
1005 \r
1006         @Override\r
1007         public boolean equals(Object obj) {\r
1008                 if (obj == this) {\r
1009                         return true;\r
1010                 }\r
1011                 if (obj != null && obj instanceof PartsSelectRow) {\r
1012                         return this.compareTo((PartsSelectRow) obj) == 0;\r
1013                 }\r
1014                 return false;\r
1015         }\r
1016 \r
1017         public int hashCode() {\r
1018                 return partsIdentifier.hashCode();\r
1019         }\r
1020 \r
1021         public PartsIdentifier getPartsIdentifier() {\r
1022                 return partsIdentifier;\r
1023         }\r
1024 \r
1025         /**\r
1026          * {@link PartsIdentifier#getLocalizedPartsName()}に委譲します.\r
1027          * @return パーツ名\r
1028          */\r
1029         public String getPartsName() {\r
1030                 return partsIdentifier.getLocalizedPartsName();\r
1031         }\r
1032 \r
1033         public boolean isChecked() {\r
1034                 return checked;\r
1035         }\r
1036 \r
1037         public void setChecked(boolean checked) {\r
1038                 this.checked = checked;\r
1039         }\r
1040 }\r
1041 \r
1042 \r
1043 /**\r
1044  * リストのモデル\r
1045  * @author seraphy\r
1046  */\r
1047 class PartsSelectListModel extends AbstractTableModel {\r
1048 \r
1049         private static final long serialVersionUID = 7604828023134579608L;\r
1050 \r
1051         private PartsCategory partsCategory;\r
1052 \r
1053         private ArrayList<PartsSelectRow> partsSelectRowList;\r
1054 \r
1055         /**\r
1056          * カテゴリが複数パーツでない場合でも選択解除を許可するフラグ.\r
1057          */\r
1058         private boolean deselectableSingleCategory;\r
1059 \r
1060         public PartsSelectListModel(PartsCategory partsCategory) {\r
1061                 if (partsCategory == null) {\r
1062                         throw new IllegalArgumentException();\r
1063                 }\r
1064                 this.partsSelectRowList = new ArrayList<PartsSelectRow>();\r
1065                 this.partsCategory = partsCategory;\r
1066         }\r
1067 \r
1068         public void load(Collection<PartsIdentifier> partsIdentifiers) {\r
1069                 if (partsIdentifiers == null) {\r
1070                         throw new IllegalArgumentException();\r
1071                 }\r
1072 \r
1073                 // 現在選択されているパーツを保存する\r
1074                 HashMap<PartsIdentifier, Integer> selectedPartsIdentifiers = new HashMap<PartsIdentifier, Integer>();\r
1075                 for (PartsIdentifier partsIdentifier : getSelectedPartsIdentifiers()) {\r
1076                         selectedPartsIdentifiers.put(partsIdentifier, selectedPartsIdentifiers.size());\r
1077                 }\r
1078 \r
1079                 // パーツイメージマップからパーツ名を列挙する.\r
1080                 ArrayList<PartsSelectRow> partsSelectList = new ArrayList<PartsSelectRow>();\r
1081                 for (PartsIdentifier partsIdentifier : partsIdentifiers) {\r
1082                         Integer selIndex = selectedPartsIdentifiers.get(partsIdentifier);\r
1083                         PartsSelectRow rowModel = new PartsSelectRow(partsIdentifier, selIndex != null);\r
1084                         // 選択されているものは、選択されているものの順序を維持する.それ以外は名前順でソートされる.\r
1085                         int order = (selIndex != null) ? selIndex.intValue() : 0;\r
1086                         rowModel.setDisplayOrder(order);\r
1087                         partsSelectList.add(rowModel);\r
1088                 }\r
1089 \r
1090                 if (partsCategory.isMultipleSelectable()) {\r
1091                         // パーツを選択有無(順序維持)・名前順に並び替える.\r
1092                         Collections.sort(partsSelectList);\r
1093                 } else {\r
1094                         // 単一選択モード時はパーツ識別子でソートする.\r
1095                         Collections.sort(partsSelectList, new Comparator<PartsSelectRow>() {\r
1096                                 public int compare(PartsSelectRow o1, PartsSelectRow o2) {\r
1097                                         return o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());\r
1098                                 }\r
1099                         });\r
1100                 }\r
1101 \r
1102                 this.partsSelectRowList = partsSelectList;\r
1103                 fireTableDataChanged();\r
1104         }\r
1105 \r
1106         /**\r
1107          * 選択選択パーツカテゴリの選択解除を許可するか?<br>\r
1108          * @return 許可する場合はtrue\r
1109          */\r
1110         public boolean isDeselectableSingleCategory() {\r
1111                 return deselectableSingleCategory;\r
1112         }\r
1113 \r
1114         /**\r
1115          * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>\r
1116          * @param deselectable 許可する場合はtrue\r
1117          */\r
1118         public void setDeselectableSingleCategory(boolean deselectable) {\r
1119                 this.deselectableSingleCategory = deselectable;\r
1120         }\r
1121 \r
1122         public PartsSelectRow getRow(int rowIndex) {\r
1123                 return partsSelectRowList.get(rowIndex);\r
1124         }\r
1125 \r
1126         public ArrayList<PartsSelectRow> getRowModelList() {\r
1127                 return this.partsSelectRowList;\r
1128         }\r
1129 \r
1130         public int getColumnCount() {\r
1131                 // ヘッダは非表示のためヘッダ名は取得する必要なし.\r
1132                 // col 0: 選択ボックス\r
1133                 // col 1: パーツ表示名\r
1134                 return 2;\r
1135         }\r
1136 \r
1137         public int getRowCount() {\r
1138                 return partsSelectRowList.size();\r
1139         }\r
1140 \r
1141         public Object getValueAt(int rowIndex, int columnIndex) {\r
1142                 PartsSelectRow rowModel = partsSelectRowList.get(rowIndex);\r
1143                 switch (columnIndex) {\r
1144                 case 0:\r
1145                         return Boolean.valueOf(rowModel.isChecked());\r
1146                 case 1:\r
1147                         return rowModel.getPartsName();\r
1148                 default:\r
1149                 }\r
1150                 return "";\r
1151         }\r
1152 \r
1153         @Override\r
1154         public Class<?> getColumnClass(int columnIndex) {\r
1155                 switch (columnIndex) {\r
1156                 case 0:\r
1157                         return Boolean.class;\r
1158                 case 1:\r
1159                         return String.class;\r
1160                 default:\r
1161                 }\r
1162                 return String.class;\r
1163         }\r
1164 \r
1165         @Override\r
1166         public boolean isCellEditable(int rowIndex, int columnIndex) {\r
1167                 if (columnIndex == 0) {\r
1168                         return true;\r
1169                 }\r
1170                 return false;\r
1171         }\r
1172 \r
1173         @Override\r
1174         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {\r
1175                 if (columnIndex != 0) {\r
1176                         return;\r
1177                 }\r
1178                 PartsSelectRow rowModel = partsSelectRowList.get(rowIndex);\r
1179                 boolean checked = ((Boolean) aValue).booleanValue();\r
1180 \r
1181                 if (!checked && rowModel.isChecked() && !deselectableSingleCategory\r
1182                                 && !partsCategory.isMultipleSelectable()) {\r
1183                         // 複数選択が可能でない場合、現在選択中のチェックは一つしかないはずのため、これを外すことはしない。\r
1184                         // ただし単一選択パーツカテゴリでの選択解除が許可されている場合を除く。\r
1185                         return;\r
1186                 }\r
1187 \r
1188                 rowModel.setChecked(checked);\r
1189 \r
1190                 // カテゴリが複数パーツ選択を許可しておらず、且つ、チェックをつけた場合、\r
1191                 // すでにチェックされている他の パーツ行のチェックを外す必要がある。\r
1192                 boolean unchecked = false;\r
1193                 if (checked && !partsCategory.isMultipleSelectable()) {\r
1194                         int mx = partsSelectRowList.size();\r
1195                         for (int idx = 0; idx < mx; idx++) {\r
1196                                 if (idx != rowIndex) {\r
1197                                         PartsSelectRow otherRow = partsSelectRowList.get(idx);\r
1198                                         if (otherRow.isChecked()) {\r
1199                                                 otherRow.setChecked(false);\r
1200                                                 unchecked = true;\r
1201                                         }\r
1202                                 }\r
1203                         }\r
1204                 }\r
1205                 if (!unchecked) {\r
1206                         // 指定されたセルの変更のみなので単一変更を通知する.\r
1207                         fireTableCellUpdated(rowIndex, columnIndex);\r
1208                 } else {\r
1209                         // 他のセルも変更されたので一括変更を通知する.\r
1210                         fireTableDataChanged();\r
1211                 }\r
1212         }\r
1213 \r
1214         /**\r
1215          * 選択されているパーツを上に、それ以外を下に振り分ける.<br>\r
1216          * それぞれはパーツの表示名順でソートされる.<br>\r
1217          */\r
1218         public void sort() {\r
1219                 int mx = partsSelectRowList.size();\r
1220                 for (int idx = 0; idx < mx; idx++) {\r
1221                         partsSelectRowList.get(idx).setDisplayOrder(idx);\r
1222                 }\r
1223                 Collections.sort(partsSelectRowList);\r
1224                 fireTableDataChanged();\r
1225         }\r
1226 \r
1227         /**\r
1228          * チェックされているパーツのパーツ識別子のリストを返す.<br>\r
1229          * リストの順序はパーツの表示されている順序と等しい.<br>\r
1230          * 選択がなければ空のリストが返される.\r
1231          * @return  チェックされているパーツのパーツ識別子のリスト、もしくは空\r
1232          */\r
1233         public List<PartsIdentifier> getSelectedPartsIdentifiers() {\r
1234                 ArrayList<PartsIdentifier> selectedRows = new ArrayList<PartsIdentifier>();\r
1235                 for (PartsSelectRow rowModel : partsSelectRowList) {\r
1236                         if (rowModel.isChecked()) {\r
1237                                 selectedRows.add(rowModel.getPartsIdentifier());\r
1238                         }\r
1239                 }\r
1240                 return selectedRows;\r
1241         }\r
1242 \r
1243         /**\r
1244          * 指定したインデックスのパーツのチェック状態を返す.\r
1245          * @param rowIndexes 調べるインデックスの配列\r
1246          * @return 引数に対応したインデックスのチェック状態、nullまたは空の場合は空を返す\r
1247          */\r
1248         public boolean[] getChecks(int[] rowIndexes) {\r
1249                 if (rowIndexes == null) {\r
1250                         rowIndexes = new int[0];\r
1251                 }\r
1252                 int mx = rowIndexes.length;\r
1253                 boolean[] results = new boolean[mx];\r
1254                 for (int idx = 0; idx < mx; idx++) {\r
1255                         int rowIndex = rowIndexes[idx];\r
1256                         PartsSelectRow row = partsSelectRowList.get(rowIndex);\r
1257                         results[idx] = row.isChecked();\r
1258                 }\r
1259                 return results;\r
1260         }\r
1261 \r
1262         /**\r
1263          * 指定したインデックスのチェック状態を設定する.\r
1264          * @param checked チェックする場合はtrue、チェックを解除する場合はfalse\r
1265          * @param selectedRows インデックスの配列、nullまたは空の場合は何もしない.\r
1266          */\r
1267         public void setChecks(boolean checked, int[] selectedRows) {\r
1268                 if (selectedRows == null || selectedRows.length == 0) {\r
1269                         return;\r
1270                 }\r
1271                 ArrayList<Integer> affectRows = new ArrayList<Integer>();\r
1272                 if (!checked) {\r
1273                         // 選択解除\r
1274                         if (!partsCategory.isMultipleSelectable()) {\r
1275                                 // 複数選択可能でない場合、選択はひとつしかないはずなので\r
1276                                 // クリアする必要はない。\r
1277                                 return;\r
1278                         }\r
1279                         // 選択を解除する.\r
1280                         for (int selRow : selectedRows) {\r
1281                                 PartsSelectRow row = partsSelectRowList.get(selRow);\r
1282                                 if (row.isChecked()) {\r
1283                                         row.setChecked(false);\r
1284                                         affectRows.add(selRow);\r
1285                                 }\r
1286                         }\r
1287                 } else {\r
1288                         // 選択\r
1289                         if (partsCategory.isMultipleSelectable()) {\r
1290                                 // 複数選択可能であれば単純に選択を有効にする\r
1291                                 for (int selRow : selectedRows) {\r
1292                                         PartsSelectRow row = partsSelectRowList.get(selRow);\r
1293                                         if (!row.isChecked()) {\r
1294                                                 row.setChecked(true);\r
1295                                                 affectRows.add(selRow);\r
1296                                         }\r
1297                                 }\r
1298                         } else {\r
1299                                 // 複数選択可能でない場合は最初のアイテムのみをチェックをつけ、\r
1300                                 // それ以外のチェックを外す.\r
1301                                 int selRow = selectedRows[0];\r
1302                                 PartsSelectRow row = partsSelectRowList.get(selRow);\r
1303                                 if (!row.isChecked()) {\r
1304                                         row.setChecked(true);\r
1305                                         affectRows.add(selRow);\r
1306                                         int mx = partsSelectRowList.size();\r
1307                                         for (int idx = 0; idx < mx; idx++) {\r
1308                                                 PartsSelectRow otherRow = partsSelectRowList.get(idx);\r
1309                                                 if (idx != selRow) {\r
1310                                                         if (otherRow.isChecked()) {\r
1311                                                                 otherRow.setChecked(false);\r
1312                                                                 affectRows.add(idx);\r
1313                                                         }\r
1314                                                 }\r
1315                                         }\r
1316                                 }\r
1317                         }\r
1318                 }\r
1319                 if (affectRows.isEmpty()) {\r
1320                         // なにも変わりないのでイベントも発生しない.\r
1321                         return;\r
1322                 }\r
1323                 // 変更された最初の行から最後の行までの範囲で変更を通知する.\r
1324                 // (変更されていない中間も含まれる)\r
1325                 int minIdx = 0;\r
1326                 int maxIdx = 0;\r
1327                 for (int idx : affectRows) {\r
1328                         minIdx = Math.min(minIdx, idx);\r
1329                         maxIdx = Math.max(maxIdx, idx);\r
1330                 }\r
1331                 fireTableRowsUpdated(minIdx, maxIdx);\r
1332         }\r
1333 }\r
1334 \r