diff --git a/gui/src/main/java/mscalc/gui/viewmodel/ScientificCalculatorViewModel.java b/gui/src/main/java/mscalc/gui/viewmodel/ScientificCalculatorViewModel.java index c2633cd..cae59b2 100644 --- a/gui/src/main/java/mscalc/gui/viewmodel/ScientificCalculatorViewModel.java +++ b/gui/src/main/java/mscalc/gui/viewmodel/ScientificCalculatorViewModel.java @@ -2,8 +2,11 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanExpression; +import javafx.beans.binding.ObjectExpression; +import javafx.beans.binding.StringExpression; import javafx.beans.property.*; import javafx.beans.value.ObservableValue; +import javafx.scene.input.KeyCode; import mscalc.engine.*; import mscalc.engine.commands.Command; import mscalc.engine.commands.IExpressionCommand; @@ -13,14 +16,13 @@ import org.apache.logging.log4j.Logger; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; public class ScientificCalculatorViewModel { private static final Logger logger = LogManager.getLogger(ScientificCalculatorViewModel.class); - - private final List allInputs = new ArrayList<>(); private final CalculatorManager calculatorManager = new CalculatorManager( @@ -42,116 +44,136 @@ public class ScientificCalculatorViewModel { public final InputViewModel clearButton = newInputViewModel() .withText("C") .withCommand(Command.CommandCLEAR) + .withKeyboardShortcut(KeyCode.ESCAPE) .build(); public final InputViewModel clearEntryButton = newInputViewModel() .withText("CE") .withCommand(Command.CommandCENTR) + .withKeyboardShortcut(KeyCode.DELETE) .build(); public final InputViewModel backspaceButton = newInputViewModel() .withText("Backspace") .withCommand(Command.CommandBACK) + .withKeyboardShortcut(KeyCode.BACK_SPACE) .build(); // --- INPUT DIGITS & CONSTANTS ---- public final InputViewModel digit0Button = newInputViewModel() .withText("0") .withCommand(Command.Command0) + .withKeyboardShortcut(KeyCode.DIGIT0) .build(); public final InputViewModel digit1Button = newInputViewModel() .withText("1") .withCommand(Command.Command1) + .withKeyboardShortcut(KeyCode.DIGIT1) .build(); public final InputViewModel digit2Button = newInputViewModel() .withText("2") .withCommand(Command.Command2) + .withKeyboardShortcut(KeyCode.DIGIT2) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(2))) .build(); public final InputViewModel digit3Button = newInputViewModel() .withText("3") .withCommand(Command.Command3) + .withKeyboardShortcut(KeyCode.DIGIT3) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(3))) .build(); public final InputViewModel digit4Button = newInputViewModel() .withText("4") .withCommand(Command.Command4) + .withKeyboardShortcut(KeyCode.DIGIT4) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(4))) .build(); public final InputViewModel digit5Button = newInputViewModel() .withText("5") .withCommand(Command.Command5) + .withKeyboardShortcut(KeyCode.DIGIT5) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(5))) .build(); public final InputViewModel digit6Button = newInputViewModel() .withText("6") .withCommand(Command.Command6) + .withKeyboardShortcut(KeyCode.DIGIT6) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(6))) .build(); public final InputViewModel digit7Button = newInputViewModel() .withText("7") .withCommand(Command.Command7) + .withKeyboardShortcut(KeyCode.DIGIT7) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(7))) .build(); public final InputViewModel digit8Button = newInputViewModel() .withText("8") .withCommand(Command.Command8) + .withKeyboardShortcut(KeyCode.DIGIT8) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(8))) .build(); public final InputViewModel digit9Button = newInputViewModel() .withText("9") .withCommand(Command.Command9) + .withKeyboardShortcut(KeyCode.DIGIT9) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(9))) .build(); public final InputViewModel digitAButton = newInputViewModel() .withText("A") .withCommand(Command.CommandA) + .withKeyboardShortcut(KeyCode.A) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xA))) .build(); public final InputViewModel digitBButton = newInputViewModel() .withText("B") .withCommand(Command.CommandB) + .withKeyboardShortcut(KeyCode.B) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xB))) .build(); public final InputViewModel digitCButton = newInputViewModel() .withText("C") .withCommand(Command.CommandC) + .withKeyboardShortcut(KeyCode.C) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xC))) .build(); public final InputViewModel digitDButton = newInputViewModel() .withText("D") .withCommand(Command.CommandD) + .withKeyboardShortcut(KeyCode.D) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xD))) .build(); public final InputViewModel digitEButton = newInputViewModel() .withText("E") .withCommand(Command.CommandE) + .withKeyboardShortcut(KeyCode.E) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xE))) .build(); public final InputViewModel digitFButton = newInputViewModel() .withText("F") .withCommand(Command.CommandF) + .withKeyboardShortcut(KeyCode.F) .withEnabled(MoreBindings.map(radixProperty, r -> r.hasDigit(0xF))) .build(); public final InputViewModel piButton = newInputViewModel() .withText("π") .withCommand(Command.CommandPI) + .withKeyboardShortcut(KeyCode.P) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); @@ -159,12 +181,14 @@ public class ScientificCalculatorViewModel { public final InputViewModel signButton = newInputViewModel() .withText("±") + .withKeyboardShortcut(KeyCode.F9) .withCommand(Command.CommandSIGN) .build(); public final InputViewModel decimalPointButton = newInputViewModel() .withText(".") .withCommand(Command.CommandPNT) + .withKeyboardShortcut(KeyCode.PERIOD) // TODO: Also supports comma .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); @@ -172,83 +196,99 @@ public class ScientificCalculatorViewModel { public final InputViewModel divideButton = newInputViewModel() .withText("/") + .withKeyboardShortcut(KeyCode.SLASH) .withCommand(Command.CommandDIV) .build(); public final InputViewModel multiplyButton = newInputViewModel() .withText("×") + .withKeyboardShortcutShiftAnd(KeyCode.DIGIT8) // TODO: What about multiply on numeric keyboard? .withCommand(Command.CommandMUL) .build(); public final InputViewModel subtractButton = newInputViewModel() .withText("-") + .withKeyboardShortcut(KeyCode.MINUS) .withCommand(Command.CommandSUB) .build(); public final InputViewModel addButton = newInputViewModel() .withText("+") + .withKeyboardShortcutShiftAnd(KeyCode.EQUALS) .withCommand(Command.CommandADD) .build(); public final InputViewModel modButton = newInputViewModel() .withText("Mod") + .withKeyboardShortcutShiftAnd(KeyCode.DIGIT5) .withCommand(Command.CommandMOD) .build(); public final InputViewModel bitOrButton = newInputViewModel() .withText("Or") + .withKeyboardShortcutShiftAnd(KeyCode.BACK_SLASH) .withCommand(Command.CommandOR) .build(); public final InputViewModel lshButton = newInputViewModel() - .withText("Lsh") - .withCommand(Command.CommandLSHF) + .withModeText("Lsh", "Rsh") + .withKeyboardShortcutShiftAnd(KeyCode.PERIOD) + .withModeCommand(Command.CommandLSHF, Command.CommandRSHF) .build(); public final InputViewModel equalsButton = newInputViewModel() .withText("=") + .withKeyboardShortcut(KeyCode.ENTER) .withCommand(Command.CommandEQU) .build(); public final InputViewModel bitAndButton = newInputViewModel() .withText("And") + .withKeyboardShortcutShiftAnd(KeyCode.DIGIT7) .withCommand(Command.CommandAnd) .build(); public final InputViewModel bitXorButton = newInputViewModel() .withText("Xor") + .withKeyboardShortcutShiftAnd(KeyCode.DIGIT6) .withCommand(Command.CommandXor) .build(); public final InputViewModel bitNotButton = newInputViewModel() .withText("Not") + .withKeyboardShortcutShiftAnd(KeyCode.BACK_QUOTE) .withCommand(Command.CommandNot) .build(); public final InputViewModel integerButton = newInputViewModel() - .withText("Int") - .withCommand(Command.CommandFloor) + .withModeText("Int", "Fra") + .withKeyboardShortcut(KeyCode.SEMICOLON) + .withCommand(Command.CommandCHOP) // TODO: Bug Invert not working .build(); // -- MEMORY FUNCTIONS --- public final InputViewModel memoryClearButton = newInputViewModel() .withText("MC") + .withKeyboardShortcutControlAnd(KeyCode.L) .withCommand(Command.CommandMCLEAR) .build(); public final InputViewModel memoryRecallButton = newInputViewModel() .withText("MR") + .withKeyboardShortcutControlAnd(KeyCode.R) .withCommand(Command.CommandRECALL) .build(); public final InputViewModel memoryStoreButton = newInputViewModel() .withText("MS") + .withKeyboardShortcutControlAnd(KeyCode.M) .withCommand(Command.CommandSTORE) .build(); public final InputViewModel memoryAddButton = newInputViewModel() .withText("M+") + .withKeyboardShortcutControlAnd(KeyCode.P) .withCommand(Command.CommandMPLUS) .build(); @@ -257,30 +297,35 @@ public class ScientificCalculatorViewModel { public final InputViewModel scientificNotationOnOffButton = newInputViewModel() .withText("F-E") .withCommand(Command.CommandFE) + .withKeyboardShortcut(KeyCode.V) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); public final InputViewModel dmsButton = newInputViewModel() .withText("dms") .withCommand(Command.CommandDMS) + .withKeyboardShortcut(KeyCode.M) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); public final InputViewModel sineButton = newInputViewModel() - .withText("sine") - .withCommand(Command.CommandSIN) + .withModeText("sin", "asin", "sinh", "asinh") + .withKeyboardShortcut(KeyCode.S) + .withModeCommand(Command.CommandSIN, Command.CommandASIN, Command.CommandSINH, Command.CommandASINH) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); public final InputViewModel cosineButton = newInputViewModel() - .withText("cos") - .withCommand(Command.CommandCOS) + .withModeText("cos", "acos", "cosh", "acosh") + .withKeyboardShortcut(KeyCode.O) + .withModeCommand(Command.CommandCOS, Command.CommandACOS, Command.CommandCOSH, Command.CommandACOSH) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); public final InputViewModel tangentButton = newInputViewModel() - .withText("tan") - .withCommand(Command.CommandTAN) + .withModeText("tan", "atan", "tanh", "atanh") + .withKeyboardShortcut(KeyCode.T) + .withModeCommand(Command.CommandTAN, Command.CommandATAN, Command.CommandTANH, Command.CommandATANH) .withEnabled(radixProperty.isEqualTo(RadixType.Decimal)) .build(); @@ -348,10 +393,38 @@ public InputViewModelBuilder newInputViewModel() { return new InputViewModelBuilder(); } + public record KeyboardCode( + KeyCode key, + boolean control, + boolean shift + ) { } + public class InputViewModelBuilder { - private StringProperty textProperty; - private ObjectProperty commandProperty; + private StringExpression textProperty; + private StringExpression tooltipProperty = new ReadOnlyStringWrapper(null); + private ObjectExpression commandProperty; private BooleanExpression enabledProperty = new ReadOnlyBooleanWrapper(true); + private KeyboardCode keyboardShortcut; + + /* + * Set text for normal, inverted, hyperbolic and hyperbolic-inverted modes. + * You may specify only one, two or four names. + */ + public InputViewModelBuilder withModeText(String text, String... modes) { + List names = new ArrayList<>(); + names.add(text); + names.addAll(Arrays.asList(modes)); + + textProperty = Bindings.createStringBinding(() -> { + boolean inv = invertedModeProperty.get(); + boolean hyp = hyperbolicModeProperty.get(); + + int index = (inv ? 1 : 0) + (hyp ? 2 : 0); + return names.get(index % names.size()); + }, invertedModeProperty, hyperbolicModeProperty); + + return this; + } public InputViewModelBuilder withText(String staticText) { return withText(new ReadOnlyStringWrapper(staticText)); @@ -362,10 +435,46 @@ public InputViewModelBuilder withText(StringProperty textProperty) { return this; } + public InputViewModelBuilder withTooltip(String staticText) { + this.tooltipProperty = new ReadOnlyStringWrapper(staticText); + return this; + } + + public InputViewModelBuilder withKeyboardShortcut(KeyCode key) { + this.keyboardShortcut = new KeyboardCode(key, false, false); + return this; + } + + public InputViewModelBuilder withKeyboardShortcutShiftAnd(KeyCode key) { + this.keyboardShortcut = new KeyboardCode(key, false, true); + return this; + } + + public InputViewModelBuilder withKeyboardShortcutControlAnd(KeyCode key) { + this.keyboardShortcut = new KeyboardCode(key, true, false); + return this; + } + public InputViewModelBuilder withCommand(Command cmd) { return withCommand(new ReadOnlyObjectWrapper<>(cmd)); } + public InputViewModelBuilder withModeCommand(Command cmd, Command... modes) { + List names = new ArrayList<>(); + names.add(cmd); + names.addAll(Arrays.asList(modes)); + + commandProperty = Bindings.createObjectBinding(() -> { + boolean inv = invertedModeProperty.get(); + boolean hyp = hyperbolicModeProperty.get(); + + int index = (inv ? 1 : 0) + (hyp ? 2 : 0); + return names.get(index % names.size()); + }, invertedModeProperty, hyperbolicModeProperty); + + return this; + } + public InputViewModelBuilder withCommand(ObjectProperty commandProperty) { this.commandProperty = commandProperty; return this; @@ -377,22 +486,25 @@ public InputViewModelBuilder withEnabled(BooleanExpression enabledProperty) { } public InputViewModel build() { - var ivm = new InputViewModel(textProperty, commandProperty, enabledProperty); + var ivm = new InputViewModel(textProperty, tooltipProperty, commandProperty, enabledProperty); allInputs.add(ivm); return ivm; } } public class InputViewModel { - private final StringProperty textProperty; - private final ObjectProperty commandProperty; + private final StringExpression textProperty; + private final StringExpression tooltipProperty; + private final ObjectExpression commandProperty; private final BooleanExpression enabledProperty; - public InputViewModel(StringProperty textProperty, - ObjectProperty commandProperty, + public InputViewModel(StringExpression textProperty, + StringExpression tooltipProperty, + ObjectExpression commandProperty, BooleanExpression enabledProperty) { this.textProperty = Objects.requireNonNull(textProperty); + this.tooltipProperty = Objects.requireNonNull(tooltipProperty); this.commandProperty = Objects.requireNonNull(commandProperty); this.enabledProperty = Objects.requireNonNull(enabledProperty); } @@ -401,11 +513,15 @@ public void execute() { calculatorManager.sendCommand(this.commandProperty.get()); } - public ReadOnlyStringProperty textProperty() { + public StringExpression textProperty() { return this.textProperty; } - public ReadOnlyObjectProperty commandProperty() { + public StringExpression tooltipProperty() { + return this.tooltipProperty; + } + + public ObjectExpression commandProperty() { return this.commandProperty; } diff --git a/gui/src/main/java/mscalc/gui/views/scientific/ScientificView.fxml b/gui/src/main/java/mscalc/gui/views/scientific/ScientificView.fxml index 08feb47..2cdb905 100644 --- a/gui/src/main/java/mscalc/gui/views/scientific/ScientificView.fxml +++ b/gui/src/main/java/mscalc/gui/views/scientific/ScientificView.fxml @@ -115,19 +115,15 @@