1 package charactermanaj.ui;
\r
3 import java.awt.BorderLayout;
\r
4 import java.awt.Component;
\r
5 import java.awt.Container;
\r
6 import java.awt.GridBagConstraints;
\r
7 import java.awt.GridBagLayout;
\r
8 import java.awt.Toolkit;
\r
9 import java.awt.event.ActionEvent;
\r
10 import java.awt.event.ActionListener;
\r
11 import java.awt.event.KeyEvent;
\r
12 import java.awt.event.WindowAdapter;
\r
13 import java.awt.event.WindowEvent;
\r
14 import java.util.ArrayList;
\r
15 import java.util.Collection;
\r
16 import java.util.Collections;
\r
17 import java.util.HashMap;
\r
18 import java.util.LinkedList;
\r
19 import java.util.List;
\r
20 import java.util.Map;
\r
21 import java.util.Properties;
\r
22 import java.util.Random;
\r
23 import java.util.concurrent.atomic.AtomicInteger;
\r
25 import javax.swing.AbstractAction;
\r
26 import javax.swing.Action;
\r
27 import javax.swing.ActionMap;
\r
28 import javax.swing.BorderFactory;
\r
29 import javax.swing.Box;
\r
30 import javax.swing.InputMap;
\r
31 import javax.swing.JButton;
\r
32 import javax.swing.JCheckBox;
\r
33 import javax.swing.JComboBox;
\r
34 import javax.swing.JComponent;
\r
35 import javax.swing.JDialog;
\r
36 import javax.swing.JFrame;
\r
37 import javax.swing.JPanel;
\r
38 import javax.swing.JRootPane;
\r
39 import javax.swing.JScrollBar;
\r
40 import javax.swing.JScrollPane;
\r
41 import javax.swing.JToggleButton;
\r
42 import javax.swing.KeyStroke;
\r
43 import javax.swing.event.EventListenerList;
\r
45 import charactermanaj.model.AppConfig;
\r
46 import charactermanaj.model.CharacterData;
\r
47 import charactermanaj.model.PartsCategory;
\r
48 import charactermanaj.model.PartsIdentifier;
\r
49 import charactermanaj.model.PartsSet;
\r
50 import charactermanaj.util.LocalizedResourcePropertyLoader;
\r
53 * パーツのランダム選択ダイアログ.<br>
\r
57 public class PartsRandomChooserDialog extends JDialog {
\r
59 private static final long serialVersionUID = -8427874726724107481L;
\r
61 protected static final String STRINGS_RESOURCE = "languages/partsrandomchooserdialog";
\r
64 * メインフレームとの間でパーツの選択状態の取得・設定を行うためのインターフェイス.<br>
\r
66 public interface PartsSetSynchronizer {
\r
69 * 現在フレームで設定されているパーツセットを取得する.
\r
73 PartsSet getCurrentPartsSet();
\r
76 * ランダム選択パネルのパーツセットでフレームを設定する.
\r
80 void setPartsSet(PartsSet partsSet);
\r
83 * 指定されたパーツがランダム選択対象外であるか?
\r
85 * @param partsIdentifier
\r
87 * @return 対象外であればtrue
\r
89 boolean isExcludePartsIdentifier(PartsIdentifier partsIdentifier);
\r
92 * 指定したパーツがランダム選択対象外であるか設定する.
\r
94 * @param partsIdentifier
\r
99 void setExcludePartsIdentifier(PartsIdentifier partsIdentifier,
\r
104 * ランダム選択パネルを縦に並べるボックス
\r
106 private Box centerPnl;
\r
111 private CharacterData characterData;
\r
116 private PartsSetSynchronizer partsSync;
\r
121 private Action actRandomAll;
\r
126 private Action actBack;
\r
131 private Action actCancel;
\r
136 private LinkedList<Map<RandomChooserPanel, PartsIdentifier>> history = new LinkedList<Map<RandomChooserPanel, PartsIdentifier>>();
\r
141 private int maxHistory;
\r
148 * @param characterData
\r
153 public PartsRandomChooserDialog(JFrame parent, CharacterData characterData,
\r
154 PartsSetSynchronizer partsSync) {
\r
155 super(parent, false);
\r
157 if (characterData == null || partsSync == null) {
\r
158 throw new IllegalArgumentException();
\r
161 setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
\r
162 addWindowListener(new WindowAdapter() {
\r
164 public void windowClosing(WindowEvent e) {
\r
169 this.characterData = characterData;
\r
170 this.partsSync = partsSync;
\r
172 AppConfig appConfig = AppConfig.getInstance();
\r
173 this.maxHistory = appConfig.getRandomChooserMaxHistory();
\r
174 if (this.maxHistory < 0) {
\r
175 this.maxHistory = 0;
\r
181 setLocationRelativeTo(parent);
\r
183 } catch (RuntimeException ex) {
\r
192 private void initLayout() {
\r
193 Properties strings = LocalizedResourcePropertyLoader
\r
194 .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);
\r
196 setTitle(strings.getProperty("partsRandomChooser"));
\r
198 Container contentPane = getContentPane();
\r
199 contentPane.setLayout(new BorderLayout());
\r
201 this.centerPnl = Box.createVerticalBox();
\r
203 ActionListener changePartsIdentifierListener = new ActionListener() {
\r
204 public void actionPerformed(ActionEvent e) {
\r
205 if (eventLock.get() == 0) {
\r
206 onChangePartsIdentifiers();
\r
211 PartsSet partsSet = partsSync.getCurrentPartsSet();
\r
212 eventLock.incrementAndGet();
\r
214 for (PartsCategory category : characterData.getPartsCategories()) {
\r
215 List<PartsIdentifier> partsIdentifiers = partsSet.get(category);
\r
216 int partsLen = (partsIdentifiers != null) ? partsIdentifiers
\r
218 boolean enable = true;
\r
219 if (partsLen < 1) {
\r
220 partsLen = 1; // 未選択の場合でも1つは作成する.
\r
221 enable = false; // 未選択の場合はディセーブルとする.
\r
224 for (int partsIdx = 0; partsIdx < partsLen; partsIdx++) {
\r
225 PartsIdentifier partsIdentifier = null;
\r
226 if (partsIdentifiers != null
\r
227 && partsIdx < partsIdentifiers.size()) {
\r
228 partsIdentifier = partsIdentifiers.get(partsIdx);
\r
230 boolean lastInCategory = (partsIdx == partsLen - 1);
\r
232 int idx = centerPnl.getComponentCount();
\r
233 RandomChooserPanel pnl = addPartsChooserPanel(centerPnl,
\r
234 idx, category, lastInCategory,
\r
235 changePartsIdentifierListener);
\r
237 // 未選択の場合、もしくは複数選択カテゴリの場合はランダムはディセーブルとする
\r
238 pnl.setEnableRandom(enable
\r
239 && !category.isMultipleSelectable());
\r
241 if (partsIdentifier != null) {
\r
242 pnl.setSelectedPartsIdentifier(partsIdentifier);
\r
248 eventLock.decrementAndGet();
\r
251 JScrollPane scr = new JScrollPane(centerPnl) {
\r
252 private static final long serialVersionUID = 1L;
\r
255 public JScrollBar createVerticalScrollBar() {
\r
256 JScrollBar sb = super.createVerticalScrollBar();
\r
257 sb.setUnitIncrement(12);
\r
261 scr.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
\r
262 contentPane.add(scr, BorderLayout.CENTER);
\r
264 this.actRandomAll = new AbstractAction(strings.getProperty("randomAll")) {
\r
265 private static final long serialVersionUID = 1L;
\r
267 public void actionPerformed(ActionEvent e) {
\r
272 this.actBack = new AbstractAction(strings.getProperty("back")) {
\r
273 private static final long serialVersionUID = 1L;
\r
275 public void actionPerformed(ActionEvent e) {
\r
280 this.actCancel = new AbstractAction(strings.getProperty("close")) {
\r
281 private static final long serialVersionUID = 1L;
\r
283 public void actionPerformed(ActionEvent e) {
\r
288 JButton btnClose = new JButton(actCancel);
\r
289 JButton btnRandomAll = new JButton(actRandomAll);
\r
290 JButton btnBack = new JButton(actBack);
\r
292 Box btnPanel = Box.createHorizontalBox();
\r
293 btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));
\r
295 btnPanel.add(btnRandomAll);
\r
296 btnPanel.add(btnBack);
\r
297 btnPanel.add(Box.createHorizontalGlue());
\r
298 btnPanel.add(btnClose);
\r
300 contentPane.add(btnPanel, BorderLayout.SOUTH);
\r
302 JRootPane rootPane = getRootPane();
\r
303 rootPane.setDefaultButton(btnRandomAll);
\r
305 Toolkit tk = Toolkit.getDefaultToolkit();
\r
306 InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
\r
307 ActionMap am = rootPane.getActionMap();
\r
308 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeDialog");
\r
309 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W,
\r
310 tk.getMenuShortcutKeyMask()), "closeDialog");
\r
311 am.put("closeDialog", actCancel);
\r
313 addHistory(getSelection());
\r
320 protected void updateUIState() {
\r
321 actBack.setEnabled(history.size() > 1);
\r
327 protected void onClose() {
\r
332 * パネル構築時、および一括ランダム選択時などでパーツのコンボボックスの選択が複数変更される場合に
\r
333 * イベントを一度だけ処理するようにグループ化するためのロック.
\r
335 private final AtomicInteger eventLock = new AtomicInteger(0);
\r
338 * センターパネル上に配置したランダム選択パネルのリストを取得する.<br>
\r
339 * (ランダム選択パネルの個数は実行時に自由に可変できるため.)
\r
341 * @return ランダム選択パネルのリスト
\r
343 protected List<RandomChooserPanel> getRandomChooserPanels() {
\r
344 ArrayList<RandomChooserPanel> panels = new ArrayList<RandomChooserPanel>();
\r
345 int mx = centerPnl.getComponentCount();
\r
346 for (int idx = 0; idx < mx; idx++) {
\r
347 Component comp = centerPnl.getComponent(idx);
\r
348 if (comp instanceof RandomChooserPanel) {
\r
349 RandomChooserPanel pnl = (RandomChooserPanel) comp;
\r
361 protected Map<RandomChooserPanel, PartsIdentifier> getSelection() {
\r
362 HashMap<RandomChooserPanel, PartsIdentifier> selection = new HashMap<RandomChooserPanel, PartsIdentifier>();
\r
364 for (RandomChooserPanel pnl : getRandomChooserPanels()) {
\r
365 PartsIdentifier partsIdentifier = pnl.getSelectedPartsIdentifier();
\r
366 selection.put(pnl, partsIdentifier);
\r
378 addHistory(Map<RandomChooserPanel, PartsIdentifier> selection) {
\r
379 if (selection == null || selection.isEmpty()) {
\r
384 history.addLast(selection);
\r
387 while (history.size() > maxHistory) {
\r
388 history.removeFirst();
\r
396 protected void onBack() {
\r
397 if (history.size() <= 1) {
\r
401 // ヒストリーの直前のものを取り出す
\r
402 // 先頭のものは現在表示中のものなので、2つ取り出す必要がある.
\r
403 history.removeLast();
\r
404 Map<RandomChooserPanel, PartsIdentifier> selection = history.getLast();
\r
406 // すべてのランダム選択パネルに再適用する.
\r
407 eventLock.incrementAndGet();
\r
409 for (Map.Entry<RandomChooserPanel, PartsIdentifier> entry : selection
\r
411 RandomChooserPanel pnl = entry.getKey();
\r
412 PartsIdentifier partsIdentifier = entry.getValue();
\r
413 pnl.setSelectedPartsIdentifier(partsIdentifier);
\r
416 PartsSet partsSet = makePartsSet(selection.values());
\r
417 if (!partsSet.isEmpty()) {
\r
418 partsSync.setPartsSet(partsSet);
\r
422 eventLock.decrementAndGet();
\r
431 protected void onRandomAll() {
\r
432 eventLock.incrementAndGet();
\r
434 for (RandomChooserPanel pnl : getRandomChooserPanels()) {
\r
435 if (pnl.isEnableRandom()) {
\r
436 // ランダム選択を有効としているものだけを対象とする.
\r
437 pnl.selectRandom();
\r
440 onChangePartsIdentifiers();
\r
443 eventLock.decrementAndGet();
\r
448 * パーツの選択からパーツセットを生成して返す.
\r
453 protected PartsSet makePartsSet(Collection<PartsIdentifier> selection) {
\r
454 PartsSet partsSet = new PartsSet();
\r
455 for (PartsIdentifier partsIdentifier : selection) {
\r
456 if (partsIdentifier != null) {
\r
457 PartsCategory category = partsIdentifier.getPartsCategory();
\r
458 partsSet.appendParts(category, partsIdentifier, null); // 色は不問とする
\r
465 * パーツの選択が変更されたことを通知される.<br>
\r
466 * 現在のランダム選択状態を、プレビューの状態に反映させる.<brr>
\r
468 protected void onChangePartsIdentifiers() {
\r
470 Map<RandomChooserPanel, PartsIdentifier> selection = getSelection();
\r
472 PartsSet partsSet = makePartsSet(selection.values());
\r
473 if (!partsSet.isEmpty()) {
\r
474 partsSync.setPartsSet(partsSet);
\r
475 addHistory(selection);
\r
484 protected class RandomChooserPanel extends JPanel {
\r
485 private static final long serialVersionUID = 1L;
\r
487 private EventListenerList listeners = new EventListenerList();
\r
489 private JCheckBox label;
\r
491 private JComboBox partsCombo;
\r
493 private JToggleButton btnReject;
\r
495 public RandomChooserPanel(final PartsCategory category,
\r
496 final boolean lastInCategory) {
\r
497 Properties strings = LocalizedResourcePropertyLoader
\r
498 .getCachedInstance().getLocalizedProperties(
\r
501 setBorder(BorderFactory.createCompoundBorder(
\r
502 BorderFactory.createEmptyBorder(3, 3, 3, 3),
\r
503 BorderFactory.createCompoundBorder(
\r
504 BorderFactory.createEtchedBorder(),
\r
505 BorderFactory.createEmptyBorder(3, 3, 3, 3))));
\r
506 setLayout(new GridBagLayout());
\r
508 GridBagConstraints gbc = new GridBagConstraints();
\r
511 gbc.gridheight = 1;
\r
513 gbc.anchor = GridBagConstraints.EAST;
\r
514 gbc.fill = GridBagConstraints.BOTH;
\r
518 String categoryName = category.getLocalizedCategoryName();
\r
519 this.label = new JCheckBox(categoryName, true);
\r
522 JButton btnRandom = new JButton(new AbstractAction(
\r
523 strings.getProperty("random")) {
\r
524 private static final long serialVersionUID = -1;
\r
526 public void actionPerformed(ActionEvent e) {
\r
532 add(btnRandom, gbc);
\r
534 ArrayList<PartsIdentifier> partsList = new ArrayList<PartsIdentifier>();
\r
535 partsList.addAll(characterData.getPartsSpecMap(category).keySet());
\r
536 Collections.sort(partsList);
\r
537 if (category.isMultipleSelectable()) {
\r
538 // 複数選択カテゴリは未選択状態が可能なため先頭に空行を入れる.
\r
539 partsList.add(0, null);
\r
542 this.partsCombo = new JComboBox(
\r
543 partsList.toArray(new PartsIdentifier[partsList.size()]));
\r
545 partsCombo.addActionListener(new ActionListener() {
\r
546 public void actionPerformed(ActionEvent e) {
\r
547 onSelectChangePartsIdentifier(e);
\r
554 add(partsCombo, gbc);
\r
556 this.btnReject = new JToggleButton(new AbstractAction(
\r
557 strings.getProperty("reject")) {
\r
558 private static final long serialVersionUID = -1;
\r
560 public void actionPerformed(ActionEvent e) {
\r
567 add(btnReject, gbc);
\r
569 if (category.isMultipleSelectable() && lastInCategory) {
\r
570 JButton btnAdd = new JButton(new AbstractAction(
\r
571 strings.getProperty("add")) {
\r
572 private static final long serialVersionUID = -1;
\r
574 public void actionPerformed(ActionEvent e) {
\r
584 updateButtonState();
\r
587 public void addActionListener(ActionListener l) {
\r
588 listeners.add(ActionListener.class, l);
\r
591 public void removeActionListener(ActionListener l) {
\r
592 listeners.remove(ActionListener.class, l);
\r
595 public boolean isEnableRandom() {
\r
596 return label.isSelected();
\r
599 public void setEnableRandom(boolean selected) {
\r
600 label.setSelected(selected);
\r
603 public PartsIdentifier getSelectedPartsIdentifier() {
\r
604 return (PartsIdentifier) partsCombo.getSelectedItem();
\r
607 public void setSelectedPartsIdentifier(PartsIdentifier partsIdentifier) {
\r
608 partsCombo.setSelectedItem(partsIdentifier);
\r
611 protected void updateButtonState() {
\r
612 PartsIdentifier partsIdentifier = getSelectedPartsIdentifier();
\r
613 if (partsIdentifier == null) {
\r
614 btnReject.setEnabled(false);
\r
617 boolean exclude = partsSync
\r
618 .isExcludePartsIdentifier(partsIdentifier);
\r
619 btnReject.setSelected(exclude);
\r
620 btnReject.setEnabled(true);
\r
623 protected void onSelectChangePartsIdentifier(ActionEvent e) {
\r
624 updateButtonState();
\r
626 ActionEvent evt = new ActionEvent(this,
\r
627 ActionEvent.ACTION_PERFORMED, "selectChangePartsIdentifier");
\r
628 for (ActionListener l : listeners
\r
629 .getListeners(ActionListener.class)) {
\r
630 l.actionPerformed(evt);
\r
634 protected void onClickReject(ActionEvent e) {
\r
635 PartsIdentifier partsIdentifier = getSelectedPartsIdentifier();
\r
636 if (partsIdentifier == null) {
\r
639 boolean exclude = partsSync
\r
640 .isExcludePartsIdentifier(partsIdentifier);
\r
641 partsSync.setExcludePartsIdentifier(partsIdentifier, !exclude);
\r
642 updateButtonState();
\r
645 protected void onClickRandom(ActionEvent e) {
\r
649 public void selectRandom() {
\r
650 ArrayList<PartsIdentifier> partsIdentifiers = new ArrayList<PartsIdentifier>();
\r
651 int mx = partsCombo.getItemCount();
\r
652 for (int idx = 0; idx < mx; idx++) {
\r
653 PartsIdentifier partsIdentifier = (PartsIdentifier) partsCombo
\r
655 if (partsIdentifier != null) {
\r
656 if (!partsSync.isExcludePartsIdentifier(partsIdentifier)) {
\r
657 partsIdentifiers.add(partsIdentifier);
\r
662 int len = partsIdentifiers.size();
\r
664 // 選択しようがないので何もしない.
\r
668 Random rng = new Random();
\r
669 int selidx = rng.nextInt(len);
\r
671 setSelectedPartsIdentifier(partsIdentifiers.get(selidx));
\r
674 protected void onClickAdd(ActionEvent e) {
\r
680 * カテゴリのパーツのランダム選択パネルを作成する.<br>
\r
681 * パネルが追加ボタンをもつときには、作成されたパネルにもパーツ変更リスナは適用される.<br>
\r
689 * @param lastInCategory
\r
690 * 作成するパネルに、追加ボタンをつけるか?
\r
691 * @param changePartsIdentifierListener
\r
693 * @return 作成されたランダム選択パネル
\r
695 protected RandomChooserPanel addPartsChooserPanel(final Box centerPnl,
\r
697 final PartsCategory category,
\r
698 final boolean lastInCategory,
\r
699 final ActionListener changePartsIdentifierListener) {
\r
700 RandomChooserPanel pnl = new RandomChooserPanel(category,
\r
702 private static final long serialVersionUID = 1L;
\r
705 protected void onClickAdd(ActionEvent e) {
\r
706 int mx = centerPnl.getComponentCount();
\r
707 for (int idx = 0; idx < mx; idx++) {
\r
708 Component comp = centerPnl.getComponent(idx);
\r
709 if (comp.equals(this)) {
\r
711 addPartsChooserPanel(centerPnl, idx + 1, category,
\r
712 lastInCategory, changePartsIdentifierListener);
\r
713 centerPnl.validate();
\r
715 ((JButton) e.getSource()).setVisible(false);
\r
722 // パーツ選択変更を通知するリスナを設定する.
\r
723 pnl.addActionListener(changePartsIdentifierListener);
\r
725 centerPnl.add(pnl, addPos);
\r