Skip to content

Commit

Permalink
Add persistent alerts widget (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwbonner authored Aug 24, 2024
1 parent 2c756ff commit f800ad2
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import edu.wpi.first.shuffleboard.api.widget.LayoutClass;
import edu.wpi.first.shuffleboard.api.widget.WidgetType;
import edu.wpi.first.shuffleboard.plugin.base.data.types.AccelerometerType;
import edu.wpi.first.shuffleboard.plugin.base.data.types.AlertsType;
import edu.wpi.first.shuffleboard.plugin.base.data.types.AnalogInputType;
import edu.wpi.first.shuffleboard.plugin.base.data.types.BasicSubsystemType;
import edu.wpi.first.shuffleboard.plugin.base.data.types.CommandType;
Expand All @@ -43,6 +44,7 @@
import edu.wpi.first.shuffleboard.plugin.base.layout.ListLayout;
import edu.wpi.first.shuffleboard.plugin.base.layout.SubsystemLayout;
import edu.wpi.first.shuffleboard.plugin.base.widget.AccelerometerWidget;
import edu.wpi.first.shuffleboard.plugin.base.widget.AlertsWidget;
import edu.wpi.first.shuffleboard.plugin.base.widget.BasicFmsInfoWidget;
import edu.wpi.first.shuffleboard.plugin.base.widget.BasicSubsystemWidget;
import edu.wpi.first.shuffleboard.plugin.base.widget.BooleanBoxWidget;
Expand Down Expand Up @@ -81,7 +83,7 @@
@Description(
group = "edu.wpi.first.shuffleboard",
name = "Base",
version = "1.3.6",
version = "1.3.7",
summary = "Defines all the WPILib data types and stock widgets"
)
@SuppressWarnings("PMD.CouplingBetweenObjects")
Expand Down Expand Up @@ -129,7 +131,8 @@ public List<DataType> getDataTypes() {
DifferentialDriveType.Instance,
FmsInfoType.Instance,
UltrasonicType.Instance,
FieldType.Instance
FieldType.Instance,
AlertsType.Instance
);
}

Expand Down Expand Up @@ -165,6 +168,7 @@ public List<ComponentType> getComponents() {
WidgetType.forAnnotatedWidget(BasicFmsInfoWidget.class),
WidgetType.forAnnotatedWidget(UltrasonicWidget.class),
WidgetType.forAnnotatedWidget(FieldWidget.class),
WidgetType.forAnnotatedWidget(AlertsWidget.class),
new LayoutClass<>("List Layout", ListLayout.class),
new LayoutClass<>("Grid Layout", GridLayout.class),
createSubsystemLayoutType()
Expand Down Expand Up @@ -198,6 +202,7 @@ public Map<DataType, ComponentType> getDefaultComponents() {
.put(UltrasonicType.Instance, WidgetType.forAnnotatedWidget(UltrasonicWidget.class))
.put(BasicSubsystemType.Instance, WidgetType.forAnnotatedWidget(BasicSubsystemWidget.class))
.put(FieldType.Instance, WidgetType.forAnnotatedWidget(FieldWidget.class))
.put(AlertsType.Instance, WidgetType.forAnnotatedWidget(AlertsWidget.class))
.put(SubsystemType.Instance, createSubsystemLayoutType())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package edu.wpi.first.shuffleboard.plugin.base.data;

import edu.wpi.first.shuffleboard.api.data.ComplexData;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.Map;

public final class AlertsData extends ComplexData<AlertsData> {

private final String[] errors;
private final String[] warnings;
private final String[] infos;

/**
* Creates a new AlertsData object.
*
* @param errors List of active error alerts
* @param warnings List of active warning alerts
* @param infos List of active info alerts
*/
public AlertsData(String[] errors, String[] warnings, String[] infos) {
this.errors = errors;
this.warnings = warnings;
this.infos = infos;
}

/**
* Gets the list of error alerts.
*/
public String[] getErrors() {
return errors;
}

/**
* Gets the list of warning alerts.
*/
public String[] getWarnings() {
return warnings;
}

/**
* Gets the list of info alerts.
*/
public String[] getInfos() {
return infos;
}

/**
* Gets a collection of all alerts.
*/
public ObservableList<AlertItem> getCollection() {
ObservableList<AlertItem> collection = FXCollections.observableArrayList();
for (String text : errors) {
collection.add(new AlertItem(AlertType.ERROR, text));
}
for (String text : warnings) {
collection.add(new AlertItem(AlertType.WARNING, text));
}
for (String text : infos) {
collection.add(new AlertItem(AlertType.INFO, text));
}
return collection;
}

@Override
public String toHumanReadableString() {
return Integer.toString(errors.length) + " error(s), " + Integer.toString(warnings.length) + " warning(s), "
+ Integer.toString(infos.length) + " info(s)";
}

@Override
public Map<String, Object> asMap() {
return Map.of("errors", errors, "warnings", warnings, "infos", infos);
}

public static class AlertItem {
public final AlertType type;
public final String text;

public AlertItem(AlertType type, String text) {
this.type = type;
this.text = text;
}
}

public static enum AlertType {
ERROR, WARNING, INFO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package edu.wpi.first.shuffleboard.plugin.base.data.types;

import edu.wpi.first.shuffleboard.api.data.ComplexDataType;
import edu.wpi.first.shuffleboard.api.util.Maps;
import edu.wpi.first.shuffleboard.plugin.base.data.AlertsData;

import java.util.Map;
import java.util.function.Function;

public final class AlertsType extends ComplexDataType<AlertsData> {

/**
* The name of data of this type as it would appear in a WPILib sendable's
* {@code .type} entry; a differential drive base a {@code .type} of
* "DifferentialDrive", a sendable chooser has it set to "String Chooser"; a
* hypothetical 2D point would have it set to "Point2D".
*/
private static final String TYPE_NAME = "Alerts";

/**
* The single instance of the point type. By convention, this is a
* {@code public static final} field and the constructor is private to ensure
* only a single instance of the data type exists.
*/
public static final AlertsType Instance = new AlertsType();

private AlertsType() {
super(TYPE_NAME, AlertsData.class);
}

@Override
public Function<Map<String, Object>, AlertsData> fromMap() {
return map -> new AlertsData(
Maps.getOrDefault(map, "errors", new String[0]),
Maps.getOrDefault(map, "warnings", new String[0]),
Maps.getOrDefault(map, "infos", new String[0])
);
}

@Override
public AlertsData getDefaultValue() {
return new AlertsData(new String[] {}, new String[] {}, new String[] {});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package edu.wpi.first.shuffleboard.plugin.base.widget;

import edu.wpi.first.shuffleboard.api.widget.Description;
import edu.wpi.first.shuffleboard.api.widget.ParametrizedController;
import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget;
import edu.wpi.first.shuffleboard.plugin.base.data.AlertsData;
import edu.wpi.first.shuffleboard.plugin.base.data.AlertsData.AlertItem;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.TextAlignment;

@Description(name = "Alerts", dataTypes = AlertsData.class, summary = "Displays a list of alerts.")
@ParametrizedController("AlertsWidget.fxml")
public final class AlertsWidget extends SimpleAnnotatedWidget<AlertsData> {

private Image errorIcon = new Image(getClass().getResourceAsStream("icons/error.png"));
private Image warningIcon = new Image(getClass().getResourceAsStream("icons/warning.png"));
private Image infoIcon = new Image(getClass().getResourceAsStream("icons/info.png"));

@FXML
private Pane root;

@FXML
private ListView<AlertItem> list;

@FXML
private GridPane placeholder;

@FXML
private void initialize() {
list.setCellFactory(param -> new ListCell<AlertItem>() {
@Override
protected void updateItem(AlertItem item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
setText(null);
} else {
setMinWidth(param.getWidth() - 32);
setMaxWidth(param.getWidth() - 32);
setPrefWidth(param.getWidth() - 32);

setWrapText(true);
setText(item.text);
setTextAlignment(TextAlignment.LEFT);

ImageView imageView = new ImageView();
switch (item.type) {
case ERROR:
imageView.setImage(errorIcon);
break;
case WARNING:
imageView.setImage(warningIcon);
break;
case INFO:
imageView.setImage(infoIcon);
break;
default:
break;
}
imageView.setFitHeight(20);
imageView.setFitWidth(20);
imageView.setTranslateX(-3);
imageView.setSmooth(true);
setGraphic(imageView);
}
}
});

list.setSelectionModel(new NoSelectionModel<AlertItem>());
list.itemsProperty().bind(dataOrDefault.map(AlertsData::getCollection));
placeholder.setAlignment(Pos.TOP_CENTER);
}

@Override
public Pane getView() {
return root;
}

private static class NoSelectionModel<T> extends MultipleSelectionModel<T> {

@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}

@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}

@Override
public void selectIndices(int index, int... indices) {
}

@Override
public void selectAll() {
}

@Override
public void selectFirst() {
}

@Override
public void selectLast() {
}

@Override
public void clearAndSelect(int index) {
}

@Override
public void select(int index) {
}

@Override
public void select(T obj) {
}

@Override
public void clearSelection(int index) {
}

@Override
public void clearSelection() {
}

@Override
public boolean isSelected(int index) {
return false;
}

@Override
public boolean isEmpty() {
return true;
}

@Override
public void selectPrevious() {
}

@Override
public void selectNext() {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="edu.wpi.first.shuffleboard.plugin.base.widget.AlertsWidget"
fx:id="root"
stylesheets="@alerts-widget.css">
<ListView fx:id="list">
<placeholder>
<GridPane fx:id="placeholder">
<Label text="(Nothing to report)"/>
</GridPane>
</placeholder>
</ListView>
</VBox>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.list-view .list-cell {
-fx-background-color: transparent;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f800ad2

Please sign in to comment.