|
18 | 18 | import javax.swing.AbstractButton; |
19 | 19 | import javax.swing.Action; |
20 | 20 | import javax.swing.ActionMap; |
| 21 | +import javax.swing.ButtonGroup; |
21 | 22 | import javax.swing.Icon; |
22 | 23 | import javax.swing.InputMap; |
23 | 24 | import javax.swing.JCheckBox; |
24 | 25 | import javax.swing.JCheckBoxMenuItem; |
25 | 26 | import javax.swing.JComponent; |
26 | 27 | import javax.swing.JLabel; |
| 28 | +import javax.swing.JMenu; |
27 | 29 | import javax.swing.JPanel; |
| 30 | +import javax.swing.JRadioButtonMenuItem; |
28 | 31 | import javax.swing.JToolTip; |
29 | 32 | import javax.swing.JTree; |
30 | 33 | import javax.swing.KeyStroke; |
|
70 | 73 | import java.util.Optional; |
71 | 74 | import java.util.function.BiConsumer; |
72 | 75 | import java.util.function.Consumer; |
| 76 | +import java.util.function.Function; |
73 | 77 | import java.util.function.Supplier; |
| 78 | +import java.util.stream.Collectors; |
| 79 | +import java.util.stream.IntStream; |
74 | 80 |
|
75 | 81 | public final class GuiUtil { |
76 | 82 | private GuiUtil() { |
@@ -472,6 +478,68 @@ private static void syncStateWithConfigImpl( |
472 | 478 | }); |
473 | 479 | } |
474 | 480 |
|
| 481 | + /** |
| 482 | + * Creates a {@link JMenu} containing one {@linkplain JRadioButtonMenuItem radio item} for each value between the |
| 483 | + * passed {@code min} and {@code max}, inclusive. |
| 484 | + * |
| 485 | + * <p> Listeners are added to keep the selected radio item and the passed {@code config}'s |
| 486 | + * {@link TrackedValue#value() value} in sync. |
| 487 | + * |
| 488 | + * @param config the config value to sync with |
| 489 | + * @param min the minimum allowed value |
| 490 | + * * this should coincide with any minimum imposed on the passed {@code config} |
| 491 | + * @param max the maximum allowed value |
| 492 | + * * this should coincide with any maximum imposed on the passed {@code config} |
| 493 | + * @param onUpdate a function to run whenever the passed {@code config} changes, whether because a radio item was |
| 494 | + * clicked or because another source updated it |
| 495 | + * |
| 496 | + * @return a newly created menu allowing configuration of the passed {@code config} |
| 497 | + */ |
| 498 | + public static JMenu createIntConfigRadioMenu( |
| 499 | + TrackedValue<Integer> config, int min, int max, Runnable onUpdate |
| 500 | + ) { |
| 501 | + final Map<Integer, JRadioButtonMenuItem> radiosByChoice = IntStream.range(min, max + 1) |
| 502 | + .boxed() |
| 503 | + .collect(Collectors.toMap( |
| 504 | + Function.identity(), |
| 505 | + choice -> { |
| 506 | + final JRadioButtonMenuItem choiceItem = new JRadioButtonMenuItem(); |
| 507 | + choiceItem.setText(Integer.toString(choice)); |
| 508 | + if (choice.equals(config.value())) { |
| 509 | + choiceItem.setSelected(true); |
| 510 | + } |
| 511 | + |
| 512 | + final int finalChoice = choice; |
| 513 | + choiceItem.addActionListener(e -> { |
| 514 | + if (!choiceItem.isSelected()) { |
| 515 | + config.setValue(finalChoice); |
| 516 | + onUpdate.run(); |
| 517 | + } |
| 518 | + }); |
| 519 | + |
| 520 | + return choiceItem; |
| 521 | + } |
| 522 | + )); |
| 523 | + |
| 524 | + final ButtonGroup choicesGroup = new ButtonGroup(); |
| 525 | + final JMenu menu = new JMenu(); |
| 526 | + for (final JRadioButtonMenuItem radio : radiosByChoice.values()) { |
| 527 | + choicesGroup.add(radio); |
| 528 | + menu.add(radio); |
| 529 | + } |
| 530 | + |
| 531 | + config.registerCallback(updated -> { |
| 532 | + final JRadioButtonMenuItem choiceItem = radiosByChoice.get(updated.value()); |
| 533 | + |
| 534 | + if (!choiceItem.isSelected()) { |
| 535 | + choiceItem.setSelected(true); |
| 536 | + onUpdate.run(); |
| 537 | + } |
| 538 | + }); |
| 539 | + |
| 540 | + return menu; |
| 541 | + } |
| 542 | + |
475 | 543 | public enum FocusCondition { |
476 | 544 | /** |
477 | 545 | * @see JComponent#WHEN_IN_FOCUSED_WINDOW |
|
0 commit comments