OSDN Git Service

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