001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.shuffleboard;
006
007import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.networktables.NetworkTable;
010import java.util.Map;
011
012/**
013 * A generic component in Shuffleboard.
014 *
015 * @param <C> the self type
016 */
017public abstract class ShuffleboardComponent<C extends ShuffleboardComponent<C>>
018    implements ShuffleboardValue {
019  private final ShuffleboardContainer m_parent;
020  private final String m_title;
021  private String m_type;
022  private Map<String, Object> m_properties;
023  private boolean m_metadataDirty = true;
024  private int m_column = -1;
025  private int m_row = -1;
026  private int m_width = -1;
027  private int m_height = -1;
028
029  protected ShuffleboardComponent(ShuffleboardContainer parent, String title, String type) {
030    m_parent = requireNonNullParam(parent, "parent", "ShuffleboardComponent");
031    m_title = requireNonNullParam(title, "title", "ShuffleboardComponent");
032    m_type = type;
033  }
034
035  protected ShuffleboardComponent(ShuffleboardContainer parent, String title) {
036    this(parent, title, null);
037  }
038
039  public final ShuffleboardContainer getParent() {
040    return m_parent;
041  }
042
043  protected final void setType(String type) {
044    m_type = type;
045    m_metadataDirty = true;
046  }
047
048  public final String getType() {
049    return m_type;
050  }
051
052  @Override
053  public final String getTitle() {
054    return m_title;
055  }
056
057  /** Gets the custom properties for this component. May be null. */
058  final Map<String, Object> getProperties() {
059    return m_properties;
060  }
061
062  /**
063   * Sets custom properties for this component. Property names are case- and whitespace-insensitive
064   * (capitalization and spaces do not matter).
065   *
066   * @param properties the properties for this component
067   * @return this component
068   */
069  @SuppressWarnings("unchecked")
070  public final C withProperties(Map<String, Object> properties) {
071    m_properties = properties;
072    m_metadataDirty = true;
073    return (C) this;
074  }
075
076  /**
077   * Sets the position of this component in the tab. This has no effect if this component is inside
078   * a layout.
079   *
080   * <p>If the position of a single component is set, it is recommended to set the positions of
081   * <i>all</i> components inside a tab to prevent Shuffleboard from automatically placing another
082   * component there before the one with the specific position is sent.
083   *
084   * @param columnIndex the column in the tab to place this component
085   * @param rowIndex the row in the tab to place this component
086   * @return this component
087   */
088  @SuppressWarnings("unchecked")
089  public final C withPosition(int columnIndex, int rowIndex) {
090    m_column = columnIndex;
091    m_row = rowIndex;
092    m_metadataDirty = true;
093    return (C) this;
094  }
095
096  /**
097   * Sets the size of this component in the tab. This has no effect if this component is inside a
098   * layout.
099   *
100   * @param width how many columns wide the component should be
101   * @param height how many rows high the component should be
102   * @return this component
103   */
104  @SuppressWarnings("unchecked")
105  public final C withSize(int width, int height) {
106    m_width = width;
107    m_height = height;
108    m_metadataDirty = true;
109    return (C) this;
110  }
111
112  protected final void buildMetadata(NetworkTable metaTable) {
113    if (!m_metadataDirty) {
114      return;
115    }
116    // Component type
117    if (getType() == null) {
118      metaTable.getEntry("PreferredComponent").delete();
119    } else {
120      metaTable.getEntry("PreferredComponent").forceSetString(getType());
121    }
122
123    // Tile size
124    if (m_width <= 0 || m_height <= 0) {
125      metaTable.getEntry("Size").delete();
126    } else {
127      metaTable.getEntry("Size").setDoubleArray(new double[] {m_width, m_height});
128    }
129
130    // Tile position
131    if (m_column < 0 || m_row < 0) {
132      metaTable.getEntry("Position").delete();
133    } else {
134      metaTable.getEntry("Position").setDoubleArray(new double[] {m_column, m_row});
135    }
136
137    // Custom properties
138    if (getProperties() != null) {
139      NetworkTable propTable = metaTable.getSubTable("Properties");
140      getProperties().forEach((name, value) -> propTable.getEntry(name).setValue(value));
141    }
142    m_metadataDirty = false;
143  }
144}