diff --git a/src/examples/java/com/readytalk/examples/swt/widgets/buttons/SquareButtonExample.java b/src/examples/java/com/readytalk/examples/swt/widgets/buttons/SquareButtonExample.java index 1cc82de..ce3ff7e 100644 --- a/src/examples/java/com/readytalk/examples/swt/widgets/buttons/SquareButtonExample.java +++ b/src/examples/java/com/readytalk/examples/swt/widgets/buttons/SquareButtonExample.java @@ -4,6 +4,7 @@ import com.readytalk.examples.swt.SwtBlingExample; import com.readytalk.swt.util.ColorFactory; import com.readytalk.swt.widgets.buttons.SquareButton; +import com.readytalk.swt.widgets.buttons.SquareButtonGroup; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; @@ -134,7 +135,7 @@ public void run(Display display, final Shell shell) { .setHoverColors(BUTTON_HOVER_COLOR_GROUP) .setDefaultColors(BUTTON_DEFAULT_COLOR_GROUP) .setClickedColors(FUN_BUTTON_CLICK_COLOR_GROUP) - .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.DefaultButtonClickHandler() { + .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.ButtonClickHandler() { @Override public void clicked() { openTestDialog(shell); @@ -168,7 +169,7 @@ public void clicked() { .setDefaultColors(FUN_BUTTON_DEFAULT_COLOR_GROUP) .setSelectedColors(FUN_BUTTON_SELECTED_COLOR_GROUP) .setClickedColors(FUN_BUTTON_CLICK_COLOR_GROUP) - .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.DefaultButtonClickHandler() { + .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.ButtonClickHandler() { @Override public void clicked() { openTestDialog(shell); @@ -190,7 +191,7 @@ public void clicked() { .setDefaultColors(MORE_FUN_BUTTON_DEFAULT_COLOR_GROUP) .setClickedColors(FUN_BUTTON_CLICK_COLOR_GROUP) .setSelectedColors(MORE_FUN_BUTTON_SELECTED_COLOR_GROUP) - .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.DefaultButtonClickHandler() { + .setDefaultMouseClickAndReturnKeyHandler(new SquareButton.ButtonClickHandler() { @Override public void clicked() { openTestDialog(shell); @@ -260,60 +261,7 @@ public void clicked() { formData.right = new FormAttachment(90); bigButtonTwo.setLayoutData(formData); - bigButtonOne.addMouseListener(new MouseAdapter() { - @Override - public void mouseUp(MouseEvent e) { - if(bigButtonTwo.isToggled()) { - bigButtonTwo.setToggled(false); - } - } - }); - - bigButtonOne.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent keyEvent) { - switch (keyEvent.character) { - case ' ': - case '\r': - case '\n': - if(bigButtonTwo.isToggled()) { - bigButtonTwo.setToggled(false); - } - break; - default: - break; - } - } - }); - - bigButtonTwo.addMouseListener(new MouseAdapter() { - @Override - public void mouseUp(MouseEvent e) { - if(bigButtonOne.isToggled()) { - bigButtonOne.setToggled(false); - } - } - }); - - bigButtonTwo.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent keyEvent) { - switch (keyEvent.character) { - case ' ': - case '\r': - case '\n': - if(bigButtonOne.isToggled()) { - bigButtonOne.setToggled(false); - } - break; - default: - break; - } - } - }); - - bigButtonOne.setToggled(true); -// shell.setSize(1200, 200); + new SquareButtonGroup(bigButtonOne, bigButtonTwo); topGroup.pack(); leftComposite.pack(); diff --git a/src/integTest/java/com/readytalk/swt/widgets/buttons/SquareButtonGroupIntegTest.java b/src/integTest/java/com/readytalk/swt/widgets/buttons/SquareButtonGroupIntegTest.java new file mode 100644 index 0000000..06ea2bf --- /dev/null +++ b/src/integTest/java/com/readytalk/swt/widgets/buttons/SquareButtonGroupIntegTest.java @@ -0,0 +1,104 @@ +package com.readytalk.swt.widgets.buttons; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Shell; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SquareButtonGroupIntegTest { + + private Display display; + private Shell shell; + + private SquareButton toggleOne, toggleTwo, toggleThree, nonToggled; + + private SquareButtonGroup group; + + @Before + public void setUp() { + display = Display.getDefault(); + shell = new Shell(display); + shell.setLayout(new FillLayout()); + + + SquareButton.SquareButtonBuilder builder = new SquareButton.SquareButtonBuilder(); + builder.setParent(shell) + .setText("one") + .setToggleable(true); + toggleOne = builder.build(); + + builder = new SquareButton.SquareButtonBuilder(); + builder.setParent(shell) + .setText("two") + .setToggleable(true); + toggleTwo = builder.build(); + + builder = new SquareButton.SquareButtonBuilder(); + builder.setParent(shell) + .setText("three") + .setToggleable(true); + toggleThree = builder.build(); + + builder = new SquareButton.SquareButtonBuilder(); + builder.setParent(shell) + .setText("non toggle"); + nonToggled = builder.build(); + + group = new SquareButtonGroup(toggleOne, toggleTwo, toggleThree, nonToggled); + + shell.open(); + } + + @Test + public void testDefaultToggle() { + Assert.assertTrue(toggleOne.isToggled()); + Assert.assertFalse(toggleTwo.isToggled()); + Assert.assertFalse(toggleThree.isToggled()); + Assert.assertFalse(nonToggled.isToggled()); + } + + @Test + public void testBasicToggle() { + toggleTwo.notifyListeners(SWT.MouseUp, new Event()); + Assert.assertFalse(toggleOne.isToggled()); + Assert.assertTrue(toggleTwo.isToggled()); + Assert.assertFalse(toggleThree.isToggled()); + Assert.assertFalse(nonToggled.isToggled()); + } + + @Test + public void testDoubleToggle() { + toggleTwo.notifyListeners(SWT.MouseUp, new Event()); + Assert.assertFalse(toggleOne.isToggled()); + Assert.assertTrue(toggleTwo.isToggled()); + Assert.assertFalse(toggleThree.isToggled()); + Assert.assertFalse(nonToggled.isToggled()); + + toggleTwo.notifyListeners(SWT.MouseUp, new Event()); + Assert.assertFalse(toggleOne.isToggled()); + Assert.assertTrue(toggleTwo.isToggled()); + Assert.assertFalse(toggleThree.isToggled()); + Assert.assertFalse(nonToggled.isToggled()); + } + + @Test + public void testForcedSelection() { + group.setCurrentlyToggledButton(toggleThree); + Assert.assertFalse(toggleOne.isToggled()); + Assert.assertFalse(toggleTwo.isToggled()); + Assert.assertTrue(toggleThree.isToggled()); + Assert.assertFalse(nonToggled.isToggled()); + } + + @After + public void tearDown() { + shell.close(); + display.dispose(); + } +} diff --git a/src/main/java/com/readytalk/swt/widgets/buttons/SquareButton.java b/src/main/java/com/readytalk/swt/widgets/buttons/SquareButton.java index d2c514c..2782df1 100644 --- a/src/main/java/com/readytalk/swt/widgets/buttons/SquareButton.java +++ b/src/main/java/com/readytalk/swt/widgets/buttons/SquareButton.java @@ -29,6 +29,8 @@ import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.TypedListener; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; /** @@ -264,15 +266,7 @@ public void handleEvent(Event e) { } void doButtonClickedColor() { - if(toggleable) { - if(toggled) { - SquareButton.this.setNormalColor(); - toggled = false; - } else { - SquareButton.this.setClickedColor(); - toggled = true; - } - } else { + if(!toggleable){ SquareButton.this.setClickedColor(); } } @@ -1227,6 +1221,12 @@ public boolean isToggled() { return toggled; } + /** + * Setting toggle states explicitly may work against what you might be trying to do, if using a SquareButtonGroup. If not + * it will work as expected. + * + * @param toggled + */ public void setToggled(boolean toggled) { this.toggled = toggled; if(toggleable) { @@ -1290,7 +1290,111 @@ protected Point getTextOrigin() { } } - public static interface DefaultButtonClickHandler { + private List strategies = new ArrayList(); + private DefaultButtonClickHandler strategyHandler; + + private DefaultButtonClickHandler defaultToggleClickHandler = new DefaultButtonClickHandler(this) { + @Override + void clicked() { + if(!disableDefaultToggleClickHandler && isToggleable()) { + if(toggled) { + setNormalColor(); + toggled = false; + } else { + setClickedColor(); + toggled = true; + } + } + } + }; + + private boolean disableDefaultToggleClickHandler; + + void setDisableDefaultToggleClickHandler(boolean disableDefaultToggleClickHandler) { + this.disableDefaultToggleClickHandler = disableDefaultToggleClickHandler; + } + + /** + * Strategies are more likely to be used in SquareButtonGroup implementations, but you can feel free to add + * any and they will all be evaluated and applied individually. + * + * @param strategy + */ + public void addStrategy(ButtonClickStrategy strategy) { + strategies.add(strategy); + addStrategyHandler(); + } + + public void clearStrategies() { + strategies.clear(); + } + + public void removeStrategy(ButtonClickStrategy strategy) { + strategies.remove(strategy); + } + + void executeStrategies() { + for(ButtonClickStrategy strategy : strategies) { + boolean validStrategy = strategy.isStrategyValid(); + if(validStrategy) { + strategy.executeStrategy(); + } + } + } + + void addStrategyHandler() { + if(strategyHandler == null) { + strategyHandler = new DefaultButtonClickHandler(this) { + @Override + void clicked() { + executeStrategies(); + } + }; + } + } + + static interface ButtonClickStrategy { + + /** + * Do whatever button logic you want to do here. + * + * @return whether or not this button click should be allowed + */ + boolean isStrategyValid(); + void executeStrategy(); + } + + /** + * Use this instead of using an explicit mouse adapter, unless you have other reason to. + */ + public static abstract class DefaultButtonClickHandler { + + public DefaultButtonClickHandler(final SquareButton button) { + button.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + clicked(); + } + }); + button.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent keyEvent) { + switch (keyEvent.character) { + case ' ': + case '\r': + case '\n': + clicked(); + break; + default: + break; + } + } + }); + } + abstract void clicked(); + } + + public static interface ButtonClickHandler { void clicked(); } @@ -1320,7 +1424,7 @@ public static class SquareButtonBuilder { protected SquareButtonColorGroup inactiveColors; protected String accessibilityName; protected boolean toggleable; - protected DefaultButtonClickHandler defaultMouseClickAndReturnKeyHandler; + protected ButtonClickHandler defaultMouseClickAndReturnKeyHandler; public SquareButtonBuilder setParent(Composite parent) { this.parent = parent; @@ -1423,7 +1527,7 @@ public SquareButtonBuilder setToggleable(boolean toggleable) { return this; } - public SquareButtonBuilder setDefaultMouseClickAndReturnKeyHandler(DefaultButtonClickHandler defaultMouseClickAndReturnKeyHandler) { + public SquareButtonBuilder setDefaultMouseClickAndReturnKeyHandler(ButtonClickHandler defaultMouseClickAndReturnKeyHandler) { this.defaultMouseClickAndReturnKeyHandler = defaultMouseClickAndReturnKeyHandler; return this; } @@ -1511,26 +1615,12 @@ public SquareButton build() throws IllegalArgumentException { } if(defaultMouseClickAndReturnKeyHandler != null) { - button.addMouseListener(new MouseAdapter() { + new DefaultButtonClickHandler(button) { @Override - public void mouseUp(MouseEvent e) { + void clicked() { defaultMouseClickAndReturnKeyHandler.clicked(); } - }); - button.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent keyEvent) { - switch (keyEvent.character) { - case ' ': - case '\r': - case '\n': - defaultMouseClickAndReturnKeyHandler.clicked(); - break; - default: - break; - } - } - }); + }; } return button; diff --git a/src/main/java/com/readytalk/swt/widgets/buttons/SquareButtonGroup.java b/src/main/java/com/readytalk/swt/widgets/buttons/SquareButtonGroup.java new file mode 100644 index 0000000..34864e9 --- /dev/null +++ b/src/main/java/com/readytalk/swt/widgets/buttons/SquareButtonGroup.java @@ -0,0 +1,105 @@ +package com.readytalk.swt.widgets.buttons; + +import java.util.ArrayList; +import java.util.List; + +/** + * Currently this is primarily used for square buttons used like a toggle. It does nothing for layouts, but acts as a point of control. + */ +public class SquareButtonGroup { + private List buttons; + private List toggleableButtons; + private SquareButton currentlyToggledButton; + + /** + * You can add any SquareButtons to a SquareButtonGroup. If they are toggleable then the group will + * link them together so they can act like radio buttons. The first button in the list will be set on by default. + * + * @param buttons + */ + public SquareButtonGroup(SquareButton ... buttons) { + this.buttons = new ArrayList(); + this.toggleableButtons = new ArrayList(); + for(SquareButton button : buttons) { + addButton(button); + } + } + + /** + * Call this to explicitly pick which button is toggled in the group. + * + * @param button + */ + public void setCurrentlyToggledButton(SquareButton button) { + setCurrentlyToggledButton(button, true); + } + + private void setCurrentlyToggledButton(SquareButton button, boolean simulateClick) { + if(simulateClick) { + if(isStrategyValid(button)) { + executeStrategy(button); + } + } else { + currentlyToggledButton = button; + button.setToggled(true); + } + } + + public SquareButton getCurrentlyToggledButton() { + return currentlyToggledButton; + } + + /** + * Internal method, will return true if the button is toggleable. + * + * @param button + * @return + */ + boolean addButton(SquareButton button) { + boolean toggleable = false; + buttons.add(button); + if(button.isToggleable()) { + button.setDisableDefaultToggleClickHandler(true); + toggleableButtons.add(button); + injectStrategy(button); + if(currentlyToggledButton == null) { + setCurrentlyToggledButton(button, false); + } + toggleable = true; + } + return toggleable; + } + + void injectStrategy(final SquareButton button) { + button.addStrategy(new SquareButton.ButtonClickStrategy() { + @Override + public boolean isStrategyValid() { + return SquareButtonGroup.this.isStrategyValid(button); + } + + @Override + public void executeStrategy() { + SquareButtonGroup.this.executeStrategy(button); + } + }); + } + + boolean isStrategyValid(SquareButton button) { + return (currentlyToggledButton != button); + } + + + void executeStrategy(SquareButton button) { + button.setToggled(true); + currentlyToggledButton = button; + for(SquareButton otherButton : toggleableButtons) { + if(currentlyToggledButton == otherButton) { + continue; + } + if(otherButton.isToggleable()) { + otherButton.setToggled(false); + } + } + } + +}