Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add persistent alerts widget #805

Merged
merged 6 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.