1 package charactermanaj.ui;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Container;
6 import java.awt.GridBagConstraints;
7 import java.awt.GridBagLayout;
8 import java.awt.Toolkit;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
11 import java.awt.event.KeyEvent;
12 import java.awt.event.WindowAdapter;
13 import java.awt.event.WindowEvent;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.List;
21 import java.util.Properties;
22 import java.util.Random;
23 import java.util.concurrent.atomic.AtomicInteger;
25 import javax.swing.AbstractAction;
26 import javax.swing.Action;
27 import javax.swing.ActionMap;
28 import javax.swing.BorderFactory;
29 import javax.swing.Box;
30 import javax.swing.InputMap;
31 import javax.swing.JButton;
32 import javax.swing.JCheckBox;
33 import javax.swing.JComboBox;
34 import javax.swing.JComponent;
35 import javax.swing.JDialog;
36 import javax.swing.JFrame;
37 import javax.swing.JPanel;
38 import javax.swing.JRootPane;
39 import javax.swing.JScrollBar;
40 import javax.swing.JScrollPane;
41 import javax.swing.JToggleButton;
42 import javax.swing.KeyStroke;
43 import javax.swing.event.EventListenerList;
45 import charactermanaj.model.AppConfig;
46 import charactermanaj.model.CharacterData;
47 import charactermanaj.model.PartsCategory;
48 import charactermanaj.model.PartsIdentifier;
49 import charactermanaj.model.PartsSet;
50 import charactermanaj.ui.util.ScaleSupport;
51 import charactermanaj.util.LocalizedResourcePropertyLoader;
54 * パーツのランダム選択ダイアログ.<br>
58 public class PartsRandomChooserDialog extends JDialog {
60 private static final long serialVersionUID = -8427874726724107481L;
62 protected static final String STRINGS_RESOURCE = "languages/partsrandomchooserdialog";
65 * メインフレームとの間でパーツの選択状態の取得・設定を行うためのインターフェイス.<br>
67 public interface PartsSetSynchronizer {
70 * 現在フレームで設定されているパーツセットを取得する.
74 PartsSet getCurrentPartsSet();
77 * ランダム選択パネルのパーツセットでフレームを設定する.
81 void setPartsSet(PartsSet partsSet);
84 * 指定されたパーツがランダム選択対象外であるか?
86 * @param partsIdentifier
90 boolean isExcludePartsIdentifier(PartsIdentifier partsIdentifier);
93 * 指定したパーツがランダム選択対象外であるか設定する.
95 * @param partsIdentifier
100 void setExcludePartsIdentifier(PartsIdentifier partsIdentifier,
105 * ランダム選択パネルを縦に並べるボックス
107 private Box centerPnl;
112 private CharacterData characterData;
117 private PartsSetSynchronizer partsSync;
122 private Action actRandomAll;
127 private Action actBack;
132 private Action actCancel;
137 private LinkedList<Map<RandomChooserPanel, PartsIdentifier>> history = new LinkedList<Map<RandomChooserPanel, PartsIdentifier>>();
142 private int maxHistory;
149 * @param characterData
154 public PartsRandomChooserDialog(JFrame parent, CharacterData characterData,
155 PartsSetSynchronizer partsSync) {
156 super(parent, false);
158 if (characterData == null || partsSync == null) {
159 throw new IllegalArgumentException();
162 setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
163 addWindowListener(new WindowAdapter() {
165 public void windowClosing(WindowEvent e) {
170 this.characterData = characterData;
171 this.partsSync = partsSync;
173 AppConfig appConfig = AppConfig.getInstance();
174 this.maxHistory = appConfig.getRandomChooserMaxHistory();
175 if (this.maxHistory < 0) {
182 setLocationRelativeTo(parent);
184 } catch (RuntimeException ex) {
193 private void initLayout() {
194 Properties strings = LocalizedResourcePropertyLoader
195 .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);
197 setTitle(strings.getProperty("partsRandomChooser"));
199 ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
201 Container contentPane = getContentPane();
202 contentPane.setLayout(new BorderLayout());
204 this.centerPnl = Box.createVerticalBox();
206 ActionListener changePartsIdentifierListener = new ActionListener() {
207 public void actionPerformed(ActionEvent e) {
208 if (eventLock.get() == 0) {
209 onChangePartsIdentifiers();
214 PartsSet partsSet = partsSync.getCurrentPartsSet();
215 eventLock.incrementAndGet();
217 for (PartsCategory category : characterData.getPartsCategories()) {
218 List<PartsIdentifier> partsIdentifiers = partsSet.get(category);
219 int partsLen = (partsIdentifiers != null) ? partsIdentifiers
221 boolean enable = true;
223 partsLen = 1; // 未選択の場合でも1つは作成する.
224 enable = false; // 未選択の場合はディセーブルとする.
227 for (int partsIdx = 0; partsIdx < partsLen; partsIdx++) {
228 PartsIdentifier partsIdentifier = null;
229 if (partsIdentifiers != null
230 && partsIdx < partsIdentifiers.size()) {
231 partsIdentifier = partsIdentifiers.get(partsIdx);
233 boolean lastInCategory = (partsIdx == partsLen - 1);
235 int idx = centerPnl.getComponentCount();
236 RandomChooserPanel pnl = addPartsChooserPanel(centerPnl,
237 idx, category, lastInCategory,
238 changePartsIdentifierListener,
241 // 未選択の場合、もしくは複数選択カテゴリの場合はランダムはディセーブルとする
242 pnl.setEnableRandom(enable
243 && !category.isMultipleSelectable());
245 if (partsIdentifier != null) {
246 pnl.setSelectedPartsIdentifier(partsIdentifier);
252 eventLock.decrementAndGet();
255 JScrollPane scr = new JScrollPane(centerPnl) {
256 private static final long serialVersionUID = 1L;
259 public JScrollBar createVerticalScrollBar() {
260 JScrollBar sb = super.createVerticalScrollBar();
261 sb.setUnitIncrement(12);
265 scr.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
266 contentPane.add(scr, BorderLayout.CENTER);
268 this.actRandomAll = new AbstractAction(strings.getProperty("randomAll")) {
269 private static final long serialVersionUID = 1L;
271 public void actionPerformed(ActionEvent e) {
276 this.actBack = new AbstractAction(strings.getProperty("back")) {
277 private static final long serialVersionUID = 1L;
279 public void actionPerformed(ActionEvent e) {
284 this.actCancel = new AbstractAction(strings.getProperty("close")) {
285 private static final long serialVersionUID = 1L;
287 public void actionPerformed(ActionEvent e) {
292 JButton btnClose = new JButton(actCancel);
293 JButton btnRandomAll = new JButton(actRandomAll);
294 JButton btnBack = new JButton(actBack);
296 Box btnPanel = Box.createHorizontalBox();
297 int mergin = (int)(5 * scaleSupport.getManualScaleX());
298 btnPanel.setBorder(BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin * 8)); // 5, 5, 5, 40
300 btnPanel.add(btnRandomAll);
301 btnPanel.add(btnBack);
302 btnPanel.add(Box.createHorizontalGlue());
303 btnPanel.add(btnClose);
305 contentPane.add(btnPanel, BorderLayout.SOUTH);
307 JRootPane rootPane = getRootPane();
308 rootPane.setDefaultButton(btnRandomAll);
310 Toolkit tk = Toolkit.getDefaultToolkit();
311 InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
312 ActionMap am = rootPane.getActionMap();
313 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeDialog");
314 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W,
315 tk.getMenuShortcutKeyMask()), "closeDialog");
316 am.put("closeDialog", actCancel);
318 addHistory(getSelection());
325 protected void updateUIState() {
326 actBack.setEnabled(history.size() > 1);
332 protected void onClose() {
337 * パネル構築時、および一括ランダム選択時などでパーツのコンボボックスの選択が複数変更される場合に
338 * イベントを一度だけ処理するようにグループ化するためのロック.
340 private final AtomicInteger eventLock = new AtomicInteger(0);
343 * センターパネル上に配置したランダム選択パネルのリストを取得する.<br>
344 * (ランダム選択パネルの個数は実行時に自由に可変できるため.)
346 * @return ランダム選択パネルのリスト
348 protected List<RandomChooserPanel> getRandomChooserPanels() {
349 ArrayList<RandomChooserPanel> panels = new ArrayList<RandomChooserPanel>();
350 int mx = centerPnl.getComponentCount();
351 for (int idx = 0; idx < mx; idx++) {
352 Component comp = centerPnl.getComponent(idx);
353 if (comp instanceof RandomChooserPanel) {
354 RandomChooserPanel pnl = (RandomChooserPanel) comp;
366 protected Map<RandomChooserPanel, PartsIdentifier> getSelection() {
367 HashMap<RandomChooserPanel, PartsIdentifier> selection = new HashMap<RandomChooserPanel, PartsIdentifier>();
369 for (RandomChooserPanel pnl : getRandomChooserPanels()) {
370 PartsIdentifier partsIdentifier = pnl.getSelectedPartsIdentifier();
371 selection.put(pnl, partsIdentifier);
383 addHistory(Map<RandomChooserPanel, PartsIdentifier> selection) {
384 if (selection == null || selection.isEmpty()) {
389 history.addLast(selection);
392 while (history.size() > maxHistory) {
393 history.removeFirst();
401 protected void onBack() {
402 if (history.size() <= 1) {
407 // 先頭のものは現在表示中のものなので、2つ取り出す必要がある.
408 history.removeLast();
409 Map<RandomChooserPanel, PartsIdentifier> selection = history.getLast();
411 // すべてのランダム選択パネルに再適用する.
412 eventLock.incrementAndGet();
414 for (Map.Entry<RandomChooserPanel, PartsIdentifier> entry : selection
416 RandomChooserPanel pnl = entry.getKey();
417 PartsIdentifier partsIdentifier = entry.getValue();
418 pnl.setSelectedPartsIdentifier(partsIdentifier);
421 PartsSet partsSet = makePartsSet(selection.values());
422 if (!partsSet.isEmpty()) {
423 partsSync.setPartsSet(partsSet);
427 eventLock.decrementAndGet();
436 protected void onRandomAll() {
437 eventLock.incrementAndGet();
439 for (RandomChooserPanel pnl : getRandomChooserPanels()) {
440 if (pnl.isEnableRandom()) {
441 // ランダム選択を有効としているものだけを対象とする.
445 onChangePartsIdentifiers();
448 eventLock.decrementAndGet();
453 * パーツの選択からパーツセットを生成して返す.
458 protected PartsSet makePartsSet(Collection<PartsIdentifier> selection) {
459 PartsSet partsSet = new PartsSet();
460 for (PartsIdentifier partsIdentifier : selection) {
461 if (partsIdentifier != null) {
462 PartsCategory category = partsIdentifier.getPartsCategory();
463 partsSet.appendParts(category, partsIdentifier, null); // 色は不問とする
470 * パーツの選択が変更されたことを通知される.<br>
471 * 現在のランダム選択状態を、プレビューの状態に反映させる.<brr>
473 protected void onChangePartsIdentifiers() {
475 Map<RandomChooserPanel, PartsIdentifier> selection = getSelection();
477 PartsSet partsSet = makePartsSet(selection.values());
478 if (!partsSet.isEmpty()) {
479 partsSync.setPartsSet(partsSet);
480 addHistory(selection);
489 protected class RandomChooserPanel extends JPanel {
490 private static final long serialVersionUID = 1L;
492 private EventListenerList listeners = new EventListenerList();
494 private JCheckBox label;
496 private JComboBox partsCombo;
498 private JToggleButton btnReject;
500 public RandomChooserPanel(final PartsCategory category,
501 final boolean lastInCategory,
502 final ScaleSupport scaleSupport) {
503 Properties strings = LocalizedResourcePropertyLoader
504 .getCachedInstance().getLocalizedProperties(
507 int gap = (int)(3 * scaleSupport.getManualScaleX());
508 setBorder(BorderFactory.createCompoundBorder(
509 BorderFactory.createEmptyBorder(gap, gap, gap, gap),
510 BorderFactory.createCompoundBorder(
511 BorderFactory.createEtchedBorder(),
512 BorderFactory.createEmptyBorder(gap, gap, gap, gap))));
513 setLayout(new GridBagLayout());
515 GridBagConstraints gbc = new GridBagConstraints();
520 gbc.anchor = GridBagConstraints.EAST;
521 gbc.fill = GridBagConstraints.BOTH;
525 String categoryName = category.getLocalizedCategoryName();
526 this.label = new JCheckBox(categoryName, true);
529 JButton btnRandom = new JButton(new AbstractAction(
530 strings.getProperty("random")) {
531 private static final long serialVersionUID = -1;
533 public void actionPerformed(ActionEvent e) {
541 ArrayList<PartsIdentifier> partsList = new ArrayList<PartsIdentifier>();
542 partsList.addAll(characterData.getPartsSpecMap(category).keySet());
543 Collections.sort(partsList);
544 if (category.isMultipleSelectable()) {
545 // 複数選択カテゴリは未選択状態が可能なため先頭に空行を入れる.
546 partsList.add(0, null);
549 this.partsCombo = new JComboBox(
550 partsList.toArray(new PartsIdentifier[partsList.size()]));
552 partsCombo.addActionListener(new ActionListener() {
553 public void actionPerformed(ActionEvent e) {
554 onSelectChangePartsIdentifier(e);
561 add(partsCombo, gbc);
563 this.btnReject = new JToggleButton(new AbstractAction(
564 strings.getProperty("reject")) {
565 private static final long serialVersionUID = -1;
567 public void actionPerformed(ActionEvent e) {
576 if (category.isMultipleSelectable() && lastInCategory) {
577 JButton btnAdd = new JButton(new AbstractAction(
578 strings.getProperty("add")) {
579 private static final long serialVersionUID = -1;
581 public void actionPerformed(ActionEvent e) {
594 public void addActionListener(ActionListener l) {
595 listeners.add(ActionListener.class, l);
598 public void removeActionListener(ActionListener l) {
599 listeners.remove(ActionListener.class, l);
602 public boolean isEnableRandom() {
603 return label.isSelected();
606 public void setEnableRandom(boolean selected) {
607 label.setSelected(selected);
610 public PartsIdentifier getSelectedPartsIdentifier() {
611 return (PartsIdentifier) partsCombo.getSelectedItem();
614 public void setSelectedPartsIdentifier(PartsIdentifier partsIdentifier) {
615 partsCombo.setSelectedItem(partsIdentifier);
618 protected void updateButtonState() {
619 PartsIdentifier partsIdentifier = getSelectedPartsIdentifier();
620 if (partsIdentifier == null) {
621 btnReject.setEnabled(false);
624 boolean exclude = partsSync
625 .isExcludePartsIdentifier(partsIdentifier);
626 btnReject.setSelected(exclude);
627 btnReject.setEnabled(true);
630 protected void onSelectChangePartsIdentifier(ActionEvent e) {
633 ActionEvent evt = new ActionEvent(this,
634 ActionEvent.ACTION_PERFORMED, "selectChangePartsIdentifier");
635 for (ActionListener l : listeners
636 .getListeners(ActionListener.class)) {
637 l.actionPerformed(evt);
641 protected void onClickReject(ActionEvent e) {
642 PartsIdentifier partsIdentifier = getSelectedPartsIdentifier();
643 if (partsIdentifier == null) {
646 boolean exclude = partsSync
647 .isExcludePartsIdentifier(partsIdentifier);
648 partsSync.setExcludePartsIdentifier(partsIdentifier, !exclude);
652 protected void onClickRandom(ActionEvent e) {
656 public void selectRandom() {
657 ArrayList<PartsIdentifier> partsIdentifiers = new ArrayList<PartsIdentifier>();
658 int mx = partsCombo.getItemCount();
659 for (int idx = 0; idx < mx; idx++) {
660 PartsIdentifier partsIdentifier = (PartsIdentifier) partsCombo
662 if (partsIdentifier != null) {
663 if (!partsSync.isExcludePartsIdentifier(partsIdentifier)) {
664 partsIdentifiers.add(partsIdentifier);
669 int len = partsIdentifiers.size();
675 Random rng = new Random();
676 int selidx = rng.nextInt(len);
678 setSelectedPartsIdentifier(partsIdentifiers.get(selidx));
681 protected void onClickAdd(ActionEvent e) {
687 * カテゴリのパーツのランダム選択パネルを作成する.<br>
688 * パネルが追加ボタンをもつときには、作成されたパネルにもパーツ変更リスナは適用される.<br>
696 * @param lastInCategory
697 * 作成するパネルに、追加ボタンをつけるか?
698 * @param changePartsIdentifierListener
700 * @return 作成されたランダム選択パネル
702 protected RandomChooserPanel addPartsChooserPanel(final Box centerPnl,
704 final PartsCategory category,
705 final boolean lastInCategory,
706 final ActionListener changePartsIdentifierListener,
707 final ScaleSupport scaleSupport) {
708 RandomChooserPanel pnl = new RandomChooserPanel(category,
709 lastInCategory, scaleSupport) {
710 private static final long serialVersionUID = 1L;
713 protected void onClickAdd(ActionEvent e) {
714 int mx = centerPnl.getComponentCount();
715 for (int idx = 0; idx < mx; idx++) {
716 Component comp = centerPnl.getComponent(idx);
717 if (comp.equals(this)) {
719 addPartsChooserPanel(centerPnl, idx + 1, category,
720 lastInCategory, changePartsIdentifierListener,
722 centerPnl.validate();
724 ((JButton) e.getSource()).setVisible(false);
731 // パーツ選択変更を通知するリスナを設定する.
732 pnl.addActionListener(changePartsIdentifierListener);
734 centerPnl.add(pnl, addPos);