Skip to content

Commit

Permalink
Fix key events (#64)
Browse files Browse the repository at this point in the history
* fix(framework): Fix issues with key event
* fix(framework): Remove sysouts
* refactor(framework): Implement requested changes
* docs(framework): Add error code to docs
  • Loading branch information
LeStegii authored Mar 4, 2024
1 parent 86ef036 commit b25a345
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 27 deletions.
21 changes: 21 additions & 0 deletions ERROR_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ public class NotAController { // Wrong, should be a controller or component
}
```

### 1010: `Method '*' annotated with @onKey in class '*' must either take no or exactly one parameter of type KeyEvent.`

- Runtime: ✅
- Annotation Processor: ✅

This error is thrown when a method annotated with `@onKey` has more than one parameter or a parameter that is not of type
`KeyEvent`.

```java

@onKey
public void onKey(String key) { // Wrong, should not have a parameter or have a parameter of type KeyEvent
// ...
}

@onKey
public void onKey(KeyEvent event, String other) { // Wrong, should not have more than one parameter
// ...
}
```

## Resources

### 2000: `Could not find resource '*'.`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.fulib.fx.annotation.controller.Resource;
import org.fulib.fx.annotation.controller.SubComponent;
import org.fulib.fx.annotation.controller.Title;
import org.fulib.fx.annotation.event.onKey;
import org.fulib.fx.annotation.param.Params;
import org.fulib.fx.annotation.param.ParamsMap;
import org.fulib.fx.util.ControllerUtil;
Expand Down Expand Up @@ -76,9 +77,21 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
checkTitle(element);
}

for (Element element : roundEnv.getElementsAnnotatedWith(onKey.class)) {
checkOnKey(element);
}

return true;
}

private void checkOnKey(Element element) {
if (element instanceof ExecutableElement method) {
if (!method.getParameters().isEmpty() && !(method.getParameters().size() == 1 && processingEnv.getTypeUtils().isAssignable(method.getParameters().get(0).asType(), processingEnv.getElementUtils().getTypeElement("javafx.scene.input.KeyEvent").asType()))) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error(1010).formatted(method.getSimpleName(), method.getEnclosingElement().asType().toString()), method);
}
}
}

private void checkResources(Element element) {
final String elementType = element.asType().toString();
if (!processingEnv.getTypeUtils().isSubtype(element.asType(), processingEnv.getElementUtils().getTypeElement("java.util.ResourceBundle").asType())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class ControllerManager {

private static ResourceBundle defaultResourceBundle;

private final Map<Object, Collection<Pair<onKey.Target, EventHandler<KeyEvent>>>> keyEventHandlers = new HashMap<>();
private final Map<Object, Collection<KeyEventHolder>> keyEventHandlers = new HashMap<>();

@Inject
Lazy<FulibFxApp> app;
Expand Down Expand Up @@ -233,20 +233,16 @@ else if (view.startsWith("#")) {
*/
private void registerKeyEvents(Object instance) {
Reflection.getMethodsWithAnnotation(instance.getClass(), onKey.class).forEach(method -> {

onKey annotation = method.getAnnotation(onKey.class);
EventType<KeyEvent> type = annotation.type().asEventType();
EventHandler<KeyEvent> handler = createKeyEventHandler(method, instance, annotation);

keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new KeyEventHolder(annotation.target(), type, handler));

switch (annotation.target()) {
case SCENE -> {
EventHandler<KeyEvent> handler = createKeyEventHandler(method, instance, annotation);
keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new Pair<>(onKey.Target.SCENE, handler));
app.get().stage().getScene().addEventFilter(type, handler);
}
case STAGE -> {
EventHandler<KeyEvent> handler = createKeyEventHandler(method, instance, annotation);
keyEventHandlers.computeIfAbsent(instance, k -> new HashSet<>()).add(new Pair<>(onKey.Target.STAGE, handler));
app.get().stage().addEventFilter(type, handler);
}
case SCENE -> app.get().stage().getScene().addEventFilter(type, handler);
case STAGE -> app.get().stage().addEventFilter(type, handler);
}
});
}
Expand Down Expand Up @@ -663,50 +659,56 @@ public void setDefaultResourceBundle(ResourceBundle resourceBundle) {
*/
private EventHandler<KeyEvent> createKeyEventHandler(Method method, Object instance, onKey annotation) {
boolean hasEventParameter = method.getParameterCount() == 1 && method.getParameterTypes()[0].isAssignableFrom(KeyEvent.class);

if (!hasEventParameter && method.getParameterCount() != 0) {
throw new RuntimeException(error(1010).formatted(method.getName(), instance.getClass().getName()));
}

method.setAccessible(true);

return event -> {
// TODO: Utility method
if ((annotation.code() == KeyCode.UNDEFINED || event.getCode() == annotation.code()) &&
(annotation.character().isEmpty() || event.getCharacter().equals(annotation.character())) &&
(annotation.text().isEmpty() || event.getText().equals(annotation.text())) &&
(event.isShiftDown() || !annotation.shift()) &&
(event.isControlDown() || !annotation.control()) &&
(event.isAltDown() || !annotation.alt()) &&
(event.isMetaDown() || !annotation.meta())
) {
if (keyEventMatchesAnnotation(event, annotation)) {
try {
if (hasEventParameter) {
method.invoke(instance, event);
} else {
method.invoke(instance);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Couldn't call method '" + method.getName() + "' in class '" + instance.getClass().getName() + "' on key event.", e);
throw new RuntimeException(error(1005).formatted(method.getName(), annotation.getClass().getSimpleName(), method.getClass()), e);
}
}
};
}

private boolean keyEventMatchesAnnotation(KeyEvent event, onKey annotation) {
return (annotation.code() == KeyCode.UNDEFINED || event.getCode() == annotation.code()) &&
(annotation.character().isEmpty() || event.getCharacter().equals(annotation.character())) &&
(annotation.text().isEmpty() || event.getText().equals(annotation.text())) &&
(event.isShiftDown() || !annotation.shift()) &&
(event.isControlDown() || !annotation.control()) &&
(event.isAltDown() || !annotation.alt()) &&
(event.isMetaDown() || !annotation.meta());
}

/**
* Clears all key handlers registered for the given instance.
*
* @param instance The instance to clear the key handlers for
*/
private void cleanUpListeners(Object instance) {
Collection<Pair<onKey.Target, EventHandler<KeyEvent>>> handlers = keyEventHandlers.get(instance);
Collection<KeyEventHolder> handlers = keyEventHandlers.get(instance);
if (handlers != null) {
for (Pair<onKey.Target, EventHandler<KeyEvent>> handler : handlers) {
switch (handler.getKey()) {
case SCENE -> app.get().stage().getScene().removeEventFilter(KeyEvent.ANY, handler.getValue());
case STAGE -> app.get().stage().removeEventFilter(KeyEvent.ANY, handler.getValue());
for (KeyEventHolder holder : handlers) {
switch (holder.target()) {
case SCENE -> app.get().stage().getScene().removeEventFilter(holder.type(), holder.handler());
case STAGE -> app.get().stage().removeEventFilter(holder.type(), holder.handler());
}
}
keyEventHandlers.remove(instance);
}
}


/**
* Returns the title of the given controller instance if it has one.
* If the title is a key, the title will be looked up in the resource bundle of the controller.
Expand Down
13 changes: 13 additions & 0 deletions framework/src/main/java/org/fulib/fx/util/KeyEventHolder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.fulib.fx.util;

import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.input.KeyEvent;
import org.fulib.fx.annotation.event.onKey;

public record KeyEventHolder(
onKey.Target target,
EventType<KeyEvent> type,
EventHandler<KeyEvent> handler
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
1007=Class '%s' is annotated with both @Controller and @Component.
1008=Method '%s' providing the view for class '%s' must not have any parameters.
1009=Class '%s' annotated with @Title must be a controller or component.
1010=Method '%s' annotated with @onKey in class '%s' must either take no or exactly one parameter of type KeyEvent.

# Resources
2000=Could not find resource '%s'.
Expand Down

0 comments on commit b25a345

Please sign in to comment.