1 package charactermanaj.ui;
3 import java.awt.BorderLayout;
5 import java.awt.Component;
6 import java.awt.Dimension;
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;
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;
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;
66 public class ImageSelectPanel extends JPanel {
68 private static final long serialVersionUID = 1L;
70 protected static final String STRINGS_RESOURCE = "languages/imageselectpanel";
77 public interface ImageSelectPanelListener extends EventListener {
83 void onSelectChange(ImageSelectPanelEvent event);
89 void onChange(ImageSelectPanelEvent event);
95 void onChangeColor(ImageSelectPanelEvent event);
101 void onPreferences(ImageSelectPanelEvent event);
107 void onTitleClick(ImageSelectPanelEvent event);
113 void onTitleDblClick(ImageSelectPanelEvent event);
121 public static class ImageSelectPanelEvent extends EventObject {
123 private static final long serialVersionUID = 1L;
125 public ImageSelectPanelEvent(ImageSelectPanel src) {
129 public ImageSelectPanel getImageSelectPanel() {
130 return (ImageSelectPanel) getSource();
138 public enum DisplayMode {
158 private static final int rowStep = 2;
163 private final LinkedList<ImageSelectPanelListener> listeners = new LinkedList<ImageSelectPanelListener>();
168 private final int rowHeight;
171 * パネルの最小高さ (ボーダー上限 + ヘッダ行の高さ)
173 private final int minHeight;
178 private int numOfVisibleRows;
183 private DisplayMode displayMode;
188 private PartsSpecResolver partsSpecResolver;
193 private final JTable partsSelectTable;
198 private final PartsSelectListModel partsSelectTableModel;
201 * 選択中のアイテム(複数選択の場合はフォーカスされているもの)、もしくはnull
203 private PartsIdentifier selectedPartsIdentifier;
206 * 選択中のアイテムのリスト(順序あり)、もしくは空
208 private List<PartsIdentifier> selectedPartsIdentifiers = Collections.emptyList();
213 private final PartsCategory partsCategory;
218 * @param partsCategory パーツカテゴリ
219 * @param partsSpecResolver キャラクターデータ
221 public ImageSelectPanel(final PartsCategory partsCategory, final PartsSpecResolver partsSpecResolver) {
222 if (partsCategory == null || partsSpecResolver == null) {
223 throw new IllegalArgumentException();
225 this.partsCategory = partsCategory;
226 this.partsSpecResolver = partsSpecResolver;
228 setLayout(new BorderLayout());
230 setBorder(BorderFactory.createCompoundBorder(
231 BorderFactory.createEmptyBorder(3, 3, 3, 3),
232 BorderFactory.createCompoundBorder(
233 BorderFactory.createEtchedBorder(),
234 BorderFactory.createEmptyBorder(3, 3, 3, 3))
238 partsSelectTableModel = new PartsSelectListModel(partsCategory);
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));
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));
255 AppConfig appConfig = AppConfig.getInstance();
257 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
258 .getLocalizedProperties(STRINGS_RESOURCE);
260 final Color selectedItemColor = appConfig.getCheckedItemBgColor();
262 partsSelectTable = new JTable(partsSelectTableModel, columnModel, selectionModel) {
263 private static final long serialVersionUID = 1L;
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()) {
270 comp.setBackground(getSelectionBackground());
273 Boolean chk = (Boolean) getModel().getValueAt(row, 0);
274 comp.setForeground(getForeground());
275 if (chk.booleanValue()) {
277 comp.setBackground(selectedItemColor);
280 comp.setBackground(getBackground());
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);
295 if (partsSpec != null) {
296 // パーツの作者名とバージョンがあれば、それを末尾につけて表示する.
297 String author = partsSpec.getAuthor();
298 double version = partsSpec.getVersion();
299 if (author != null) {
301 suffix = " (" + author + " " + version + ")";
303 suffix = " (" + author + ")";
307 return partsIdentifier.getLocalizedPartsName() + suffix;
312 partsSelectTable.addFocusListener(new FocusAdapter() {
314 public void focusGained(FocusEvent e) {
315 partsSelectTable.repaint();
318 public void focusLost(FocusEvent e) {
319 partsSelectTable.repaint();
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) {
330 partsSelectTablePopupMenu.add(actDeselectAll);
332 partsSelectTable.addMouseListener(new MouseAdapter() {
334 public void mousePressed(MouseEvent e) {
338 public void mouseReleased(MouseEvent e) {
341 private void evaluatePopup(MouseEvent e) {
342 if ((partsCategory.isMultipleSelectable() || isDeselectableSingleCategory())
343 && e.isPopupTrigger()) {
344 partsSelectTablePopupMenu.show(partsSelectTable, e.getX(), e.getY());
349 partsSelectTableModel.addTableModelListener(new TableModelListener() {
350 public void tableChanged(TableModelEvent e) {
351 if (e.getType() == TableModelEvent.UPDATE) {
352 onChange(new ImageSelectPanelEvent(ImageSelectPanel.this));
356 partsSelectTable.setSelectionBackground(appConfig.getSelectedItemBgColor());
357 if (partsCategory.isMultipleSelectable()) {
358 partsSelectTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
360 partsSelectTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
362 partsSelectTable.setRowSelectionAllowed(true);
363 partsSelectTable.setTableHeader(null);
364 partsSelectTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
365 partsSelectTable.setShowVerticalLines(false);
366 partsSelectTable.setShowHorizontalLines(false);
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) {
383 if (checks.length == checkedCount) {
384 // 選択しているアイテムのすべてがチェック済みである
385 partsSelectTableModel.setChecks(false, selectedRows);
387 // 選択しているアイテムの一部もしくは全部がチェックされていない
388 partsSelectTableModel.setChecks(true, selectedRows);
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());
399 JScrollPane scrollPane = new JScrollPane(partsSelectTable);
400 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
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");
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"));
413 leftBtn.addActionListener(new ActionListener() {
414 public void actionPerformed(ActionEvent e) {
415 if (isMinimizeMode()) {
416 setMinimizeMode(false);
422 rightBtn.addActionListener(new ActionListener() {
423 public void actionPerformed(ActionEvent e) {
424 if (isMinimizeMode()) {
425 setMinimizeMode(false);
431 colorBtn.addActionListener(new ActionListener() {
432 public void actionPerformed(ActionEvent e) {
436 configBtn.addActionListener(new ActionListener() {
437 public void actionPerformed(ActionEvent e) {
443 JPanel btnPanelGrp = new JPanel(new BorderLayout());
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); // 設定ボタン (現在は非表示)
452 btnPanelGrp.add(toolBar, BorderLayout.NORTH);
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");
460 upBtn.setToolTipText(strings.getProperty("tooltip.up"));
461 downBtn.setToolTipText(strings.getProperty("tooltip.down"));
462 sortBtn.setToolTipText(strings.getProperty("tooltip.sort"));
464 upBtn.addActionListener(new ActionListener() {
465 public void actionPerformed(ActionEvent e) {
469 downBtn.addActionListener(new ActionListener() {
470 public void actionPerformed(ActionEvent e) {
474 sortBtn.addActionListener(new ActionListener() {
475 public void actionPerformed(ActionEvent e) {
480 JToolBar toolBar2 = new JToolBar();
481 toolBar2.setFloatable(false);
483 toolBar2.add(downBtn);
484 toolBar2.add(sortBtn);
485 btnPanelGrp.add(toolBar2, BorderLayout.SOUTH);
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();
496 title.addMouseListener(new MouseAdapter() {
498 public void mousePressed(MouseEvent e) {
499 if (e.getClickCount() == 2) {
502 } else if (e.getClickCount() == 1) {
508 public void mouseEntered(MouseEvent e) {
509 title.setForeground(hilightColor);
512 public void mouseExited(MouseEvent e) {
513 title.setForeground(defaultTitleColor);
517 header.add(title, BorderLayout.CENTER);
519 add(header, BorderLayout.NORTH);
520 add(scrollPane, BorderLayout.CENTER);
522 rowHeight = partsSelectTable.getRowHeight();
524 // パネルの最小高さ (ボーダー上下 + ヘッダ行高さ)
525 Insets insets = getInsets();
526 minHeight = header.getPreferredSize().height + insets.top + insets.bottom;
529 Dimension dim = new Dimension(200, 200);
530 setPreferredSize(dim);
533 numOfVisibleRows = partsCategory.getVisibleRows();
534 setDisplayMode(DisplayMode.NORMAL);
538 * 表示行数から推奨のパネル高さを求める.<br>
539 * パネル高さは1行の高さ x 表示行数 + ヘッダ + ボーダー上下である.<br>
540 * @param numOfVisibleRows 表示行数
543 protected int calcPreferredHeight(int numOfVisibleRows) {
544 return minHeight + Math.max(0, rowHeight * numOfVisibleRows);
548 * パーツをパネルにロードします.<br>
549 * 既存の内容はリセットされたのち、現在の選択パーツ位置にスクロールします.<br>
551 public void loadParts() {
552 partsSelectTableModel.load(partsSpecResolver.getPartsSpecMap(partsCategory).keySet());
553 scrollToSelectedRow();
557 * このイメージ選択パネルの該当カテゴリを返します.<br>
560 public PartsCategory getPartsCategory() {
561 return partsCategory;
565 * 現在選択している、すべてのパーツの選択を解除します.<br>
566 * 単一選択カテゴリであるかどうかを問わず、常にすべて解除されます.<br>
569 public void deselectAll() {
570 PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel();
571 ArrayList<PartsSelectRow> rowModels = rowModelList.getRowModelList();
574 for (PartsSelectRow rowModel : rowModels) {
575 rowModel.setChecked(false);
578 // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません.
579 // そのため再描画させる必要があります.
580 partsSelectTable.repaint();
582 // アイテムの選択が変更されたことを通知する.
583 onChange(new ImageSelectPanelEvent(ImageSelectPanel.this));
587 * カテゴリのリストでパーツを選択しなおします.<br>
589 * @param partsIdentifiers
591 public void selectParts(Collection<PartsIdentifier> partsIdentifiers) {
592 if (partsIdentifiers == null) {
593 partsIdentifiers = Collections.emptyList();
595 PartsSelectListModel rowModelList = (PartsSelectListModel) partsSelectTable.getModel();
596 ArrayList<PartsSelectRow> rowModels = rowModelList.getRowModelList();
598 for (PartsSelectRow rowModel : rowModels) {
599 rowModel.setChecked(false);
602 ArrayList<PartsIdentifier> partsIdentifiersBuf = new ArrayList<PartsIdentifier>(partsIdentifiers);
603 Collections.reverse(partsIdentifiersBuf);
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()) {
613 rowModels.add(0, rowModel);
621 selectedPartsIdentifier = getSelectedPartsIdentifier();
622 selectedPartsIdentifiers = getSelectedPartsIdentifiers();
624 // コンポーネントではなく、モデルに対する直接変更であるため、イベントは発生しません.
625 // そのため再描画させる必要があります.
626 partsSelectTable.repaint();
628 // あたらしく選択されたアイテムが表示されるようにスクロールします.
629 scrollToSelectedRow();
633 * カテゴリのリストで選択中のアイテムが見えるようにスクロールする.
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);
650 * 最小化の場合は、高さは表示行数ゼロとなりタイトルとボーダーだけとなる.<br>
651 * 最小化解除した場合は、標準高さは既定、最大サイズはフリーとなる.<br>
652 * @param shrinkMode 最小化モードならばtrue、フリーモードならばfalse
654 public void setMinimizeMode(boolean minimizeMode) {
655 setDisplayMode(minimizeMode ? DisplayMode.MINIMIZED : DisplayMode.EXPANDED);
659 * 表示モードを切り替えパネルサイズを調整する.<br>
660 * @param displayMode 表示モード
662 public void setDisplayMode(DisplayMode displayMode) {
663 if (displayMode == null) {
664 displayMode = DisplayMode.NORMAL;
667 Dimension siz = getPreferredSize();
668 Dimension sizMax = getMaximumSize();
670 if (displayMode == DisplayMode.MINIMIZED) {
671 int preferredHeight = calcPreferredHeight(0);
672 siz.height = preferredHeight;
673 sizMax.height = preferredHeight;
675 } else if (displayMode == DisplayMode.EXPANDED) {
676 int preferredHeight = calcPreferredHeight(numOfVisibleRows);
677 siz.height = preferredHeight;
678 sizMax.height = Integer.MAX_VALUE;
681 // DisplayMode.NORMALの場合
682 int preferredHeight = calcPreferredHeight(numOfVisibleRows);
683 siz.height = preferredHeight;
684 sizMax.height = preferredHeight;
687 setPreferredSize(siz);
689 setMaximumSize(sizMax);
691 this.displayMode = displayMode;
695 public DisplayMode getDisplayMode() {
699 public boolean isMinimizeMode() {
700 return displayMode == DisplayMode.MINIMIZED;
705 * ただし、ヘッダ部よりは小さくならない.<br>
706 * 現在の表示モードが標準でなければ縮小せず標準に戻す.<br>
708 public void shrink() {
709 if (displayMode == DisplayMode.NORMAL) {
711 numOfVisibleRows = Math.max(0, numOfVisibleRows - rowStep);
714 setDisplayMode(DisplayMode.NORMAL);
719 * 現在の表示モードが標準でなければ拡大前せず標準に戻す.<br>
721 public void expand() {
722 if (displayMode == DisplayMode.NORMAL) {
724 numOfVisibleRows += Math.max(0, rowStep);
727 setDisplayMode(DisplayMode.NORMAL);
730 public void addImageSelectListener(ImageSelectPanelListener listener) {
731 if (listener == null) {
732 throw new IllegalArgumentException();
734 listeners.add(listener);
737 public void removeImageSelectListener(ImageSelectPanelListener listener) {
738 listeners.remove(listener);
741 public void requestListFocus() {
742 partsSelectTable.requestFocus();
746 * 指定したパーツ識別子にフォーカスを当てます.<br>
747 * 必要に応じてスクロールされます.<br>
748 * 該当するパーツ識別子がなければ何もしません.<br>
749 * @param partsIdentifier パーツ識別子
751 public void setSelection(PartsIdentifier partsIdentifier) {
752 if (partsIdentifier == null) {
755 PartsCategory partsCategory = partsIdentifier.getPartsCategory();
756 if (!this.partsCategory.equals(partsCategory)) {
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();
775 * フォーカスのあるアイテムを1つ上に移動します.
777 protected void onUp() {
778 int selRow = partsSelectTable.getSelectedRow();
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));
792 partsSelectTable.repaint();
793 partsSelectTable.requestFocus();
797 * フォーカスのあるアイテムを1つ下に移動します.
799 protected void onDown() {
800 int selRow = partsSelectTable.getSelectedRow();
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));
815 partsSelectTable.repaint();
816 partsSelectTable.requestFocus();
820 * 選択中のアイテムを選択順序を維持したまま上側に、それ以外は名前順で下側に集めるようにソートします.<br>
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();
830 partsSelectTable.requestFocus();
836 protected void onTitleClick() {
837 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);
838 for (ImageSelectPanelListener listener : listeners) {
839 listener.onTitleClick(event);
846 protected void onTitleDblClick() {
847 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);
848 for (ImageSelectPanelListener listener : listeners) {
849 listener.onTitleDblClick(event);
857 protected void onChangeColor() {
858 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);
859 for (ImageSelectPanelListener listener : listeners) {
860 listener.onChangeColor(event);
868 protected void onPreferences() {
869 ImageSelectPanelEvent event = new ImageSelectPanelEvent(this);
870 for (ImageSelectPanelListener listener : listeners) {
871 listener.onPreferences(event);
876 * アイテムのチェック状態が変更された場合.
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);
886 onSelectChange(event);
891 * アイテムの選択(フォーカス)が変更された場合.
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);
905 * 使用中のアイテムの一覧を返す.(選択順)<br>
906 * @return 使用中のアイテムの一覧.(選択順)、ひとつもなければ空
908 public List<PartsIdentifier> getSelectedPartsIdentifiers() {
909 return partsSelectTableModel.getSelectedPartsIdentifiers();
914 * 複数選択可能である場合は、使用中のアイテムでフォーカスがある最初のアイテムを返す.<br>
915 * 単一選択の場合は、最初の使用中アイテムを返す.<br>
916 * 複数選択可能で、使用中のアイテムにひとつもフォーカスがあたってない場合は、
918 * 使用中アイテムがなければnullを返す.
919 * @return 使用中アイテム、もしくはnull
921 public PartsIdentifier getSelectedPartsIdentifier() {
923 // フォーカスがあたっていて、且つ、チェック状態のアイテムを上から順に走査し、
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();
934 // チェック状態のアイテムの最初のものを返す.
935 List<PartsIdentifier> checkedRows = getSelectedPartsIdentifiers();
936 if (checkedRows.size() > 0) {
937 return checkedRows.get(0);
945 * 選択選択パーツカテゴリの選択解除を許可するか?<br>
946 * @return 許可する場合はtrue
948 public boolean isDeselectableSingleCategory() {
949 return partsSelectTableModel.isDeselectableSingleCategory();
953 * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>
954 * @param deselectable 許可する場合はtrue
956 public void setDeselectableSingleCategory(boolean deselectable) {
957 partsSelectTableModel.setDeselectableSingleCategory(deselectable);
964 * パーツデータ、表示名と使用中フラグを管理する.
967 final class PartsSelectRow implements Comparable<PartsSelectRow> {
969 private PartsIdentifier partsIdentifier;
971 private boolean checked;
973 private int displayOrder;
975 public PartsSelectRow(final PartsIdentifier partsIdentifier, final boolean checked) {
976 this.partsIdentifier = partsIdentifier;
977 this.checked = checked;
981 * 選択されているものを上、そうでないものを下に振り分ける。
982 * 選択されているもの同士、選択されていないもの同士は、互いのディスプレイ順でソートされる.<br>
983 * 選択されているもの同士、選択されていないもの同士で、且つ、同一のディスプレイ順序であればパーツの表示名順でソートされる.<br>
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;
993 ret = partsIdentifier.compareTo(o.partsIdentifier);
998 public void setDisplayOrder(int displayOrder) {
999 this.displayOrder = displayOrder;
1002 public int getDisplayOrder() {
1003 return this.displayOrder;
1007 public boolean equals(Object obj) {
1011 if (obj != null && obj instanceof PartsSelectRow) {
1012 return this.compareTo((PartsSelectRow) obj) == 0;
1017 public int hashCode() {
1018 return partsIdentifier.hashCode();
1021 public PartsIdentifier getPartsIdentifier() {
1022 return partsIdentifier;
1026 * {@link PartsIdentifier#getLocalizedPartsName()}に委譲します.
1029 public String getPartsName() {
1030 return partsIdentifier.getLocalizedPartsName();
1033 public boolean isChecked() {
1037 public void setChecked(boolean checked) {
1038 this.checked = checked;
1047 class PartsSelectListModel extends AbstractTableModel {
1049 private static final long serialVersionUID = 7604828023134579608L;
1051 private PartsCategory partsCategory;
1053 private ArrayList<PartsSelectRow> partsSelectRowList;
1056 * カテゴリが複数パーツでない場合でも選択解除を許可するフラグ.
1058 private boolean deselectableSingleCategory;
1060 public PartsSelectListModel(PartsCategory partsCategory) {
1061 if (partsCategory == null) {
1062 throw new IllegalArgumentException();
1064 this.partsSelectRowList = new ArrayList<PartsSelectRow>();
1065 this.partsCategory = partsCategory;
1068 public void load(Collection<PartsIdentifier> partsIdentifiers) {
1069 if (partsIdentifiers == null) {
1070 throw new IllegalArgumentException();
1073 // 現在選択されているパーツを保存する
1074 HashMap<PartsIdentifier, Integer> selectedPartsIdentifiers = new HashMap<PartsIdentifier, Integer>();
1075 for (PartsIdentifier partsIdentifier : getSelectedPartsIdentifiers()) {
1076 selectedPartsIdentifiers.put(partsIdentifier, selectedPartsIdentifiers.size());
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);
1090 if (partsCategory.isMultipleSelectable()) {
1091 // パーツを選択有無(順序維持)・名前順に並び替える.
1092 Collections.sort(partsSelectList);
1094 // 単一選択モード時はパーツ識別子でソートする.
1095 Collections.sort(partsSelectList, new Comparator<PartsSelectRow>() {
1096 public int compare(PartsSelectRow o1, PartsSelectRow o2) {
1097 return o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
1102 this.partsSelectRowList = partsSelectList;
1103 fireTableDataChanged();
1107 * 選択選択パーツカテゴリの選択解除を許可するか?<br>
1108 * @return 許可する場合はtrue
1110 public boolean isDeselectableSingleCategory() {
1111 return deselectableSingleCategory;
1115 * 選択選択パーツカテゴリの選択解除を許可するか設定する.<br>
1116 * @param deselectable 許可する場合はtrue
1118 public void setDeselectableSingleCategory(boolean deselectable) {
1119 this.deselectableSingleCategory = deselectable;
1122 public PartsSelectRow getRow(int rowIndex) {
1123 return partsSelectRowList.get(rowIndex);
1126 public ArrayList<PartsSelectRow> getRowModelList() {
1127 return this.partsSelectRowList;
1130 public int getColumnCount() {
1131 // ヘッダは非表示のためヘッダ名は取得する必要なし.
1137 public int getRowCount() {
1138 return partsSelectRowList.size();
1141 public Object getValueAt(int rowIndex, int columnIndex) {
1142 PartsSelectRow rowModel = partsSelectRowList.get(rowIndex);
1143 switch (columnIndex) {
1145 return Boolean.valueOf(rowModel.isChecked());
1147 return rowModel.getPartsName();
1154 public Class<?> getColumnClass(int columnIndex) {
1155 switch (columnIndex) {
1157 return Boolean.class;
1159 return String.class;
1162 return String.class;
1166 public boolean isCellEditable(int rowIndex, int columnIndex) {
1167 if (columnIndex == 0) {
1174 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
1175 if (columnIndex != 0) {
1178 PartsSelectRow rowModel = partsSelectRowList.get(rowIndex);
1179 boolean checked = ((Boolean) aValue).booleanValue();
1181 if (!checked && rowModel.isChecked() && !deselectableSingleCategory
1182 && !partsCategory.isMultipleSelectable()) {
1183 // 複数選択が可能でない場合、現在選択中のチェックは一つしかないはずのため、これを外すことはしない。
1184 // ただし単一選択パーツカテゴリでの選択解除が許可されている場合を除く。
1188 rowModel.setChecked(checked);
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);
1206 // 指定されたセルの変更のみなので単一変更を通知する.
1207 fireTableCellUpdated(rowIndex, columnIndex);
1209 // 他のセルも変更されたので一括変更を通知する.
1210 fireTableDataChanged();
1215 * 選択されているパーツを上に、それ以外を下に振り分ける.<br>
1216 * それぞれはパーツの表示名順でソートされる.<br>
1218 public void sort() {
1219 int mx = partsSelectRowList.size();
1220 for (int idx = 0; idx < mx; idx++) {
1221 partsSelectRowList.get(idx).setDisplayOrder(idx);
1223 Collections.sort(partsSelectRowList);
1224 fireTableDataChanged();
1228 * チェックされているパーツのパーツ識別子のリストを返す.<br>
1229 * リストの順序はパーツの表示されている順序と等しい.<br>
1230 * 選択がなければ空のリストが返される.
1231 * @return チェックされているパーツのパーツ識別子のリスト、もしくは空
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());
1240 return selectedRows;
1244 * 指定したインデックスのパーツのチェック状態を返す.
1245 * @param rowIndexes 調べるインデックスの配列
1246 * @return 引数に対応したインデックスのチェック状態、nullまたは空の場合は空を返す
1248 public boolean[] getChecks(int[] rowIndexes) {
1249 if (rowIndexes == null) {
1250 rowIndexes = new int[0];
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();
1263 * 指定したインデックスのチェック状態を設定する.
1264 * @param checked チェックする場合はtrue、チェックを解除する場合はfalse
1265 * @param selectedRows インデックスの配列、nullまたは空の場合は何もしない.
1267 public void setChecks(boolean checked, int[] selectedRows) {
1268 if (selectedRows == null || selectedRows.length == 0) {
1271 ArrayList<Integer> affectRows = new ArrayList<Integer>();
1274 if (!partsCategory.isMultipleSelectable()) {
1275 // 複数選択可能でない場合、選択はひとつしかないはずなので
1280 for (int selRow : selectedRows) {
1281 PartsSelectRow row = partsSelectRowList.get(selRow);
1282 if (row.isChecked()) {
1283 row.setChecked(false);
1284 affectRows.add(selRow);
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);
1299 // 複数選択可能でない場合は最初のアイテムのみをチェックをつけ、
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);
1319 if (affectRows.isEmpty()) {
1320 // なにも変わりないのでイベントも発生しない.
1323 // 変更された最初の行から最後の行までの範囲で変更を通知する.
1324 // (変更されていない中間も含まれる)
1327 for (int idx : affectRows) {
1328 minIdx = Math.min(minIdx, idx);
1329 maxIdx = Math.max(maxIdx, idx);
1331 fireTableRowsUpdated(minIdx, maxIdx);