aboutsummaryrefslogtreecommitdiff
path: root/src/main/ui/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/ui/widgets')
-rw-r--r--src/main/ui/widgets/CertEditDialog.java108
-rw-r--r--src/main/ui/widgets/CertTableModel.java129
-rw-r--r--src/main/ui/widgets/GCBuilder.java213
-rw-r--r--src/main/ui/widgets/LogTableModel.java79
-rw-r--r--src/main/ui/widgets/TemplateTableModel.java105
-rw-r--r--src/main/ui/widgets/UIUtils.java168
6 files changed, 802 insertions, 0 deletions
diff --git a/src/main/ui/widgets/CertEditDialog.java b/src/main/ui/widgets/CertEditDialog.java
new file mode 100644
index 0000000..ea16b19
--- /dev/null
+++ b/src/main/ui/widgets/CertEditDialog.java
@@ -0,0 +1,108 @@
+package ui.widgets;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.WEST;
+import static ui.widgets.UIUtils.btn;
+
+/**
+ * A common dialog for cert / template editing. It will close upon Esc or Cancel.
+ * ┌───────────────────────────┐
+ * │ Dialog X │
+ * │ │
+ * │Template: (TBD) │
+ * │Subject: _________ │
+ * │Validity (Days): (Spinner)│
+ * │ │
+ * │ Button Cancel│
+ * └───────────────────────────┘
+ */
+public abstract class CertEditDialog<T> extends JDialog {
+ /**
+ * The result.
+ */
+ protected T res;
+
+ /**
+ * Root pane.
+ */
+ protected JPanel contentPane = new JPanel();
+ protected JButton buttonOK = btn("", this::onOK);
+ protected JTextField textFieldSubject = new JTextField();
+ protected JSpinner spinnerValidity =
+ new JSpinner(new SpinnerNumberModel(60, 1, null, 0));
+
+ /**
+ * EFFECTS: Render the dialog, leaving title and OK button text blank.
+ */
+ public CertEditDialog() {
+ contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.PAGE_AXIS));
+ contentPane.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ contentPane.add(renderForm());
+
+ contentPane.add(UIUtils.createActionsPane(buttonOK, btn("Cancel", this::onCancel)));
+
+ setContentPane(contentPane);
+ setModal(true);
+ getRootPane().setDefaultButton(buttonOK);
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ onCancel(null);
+ }
+ });
+
+ contentPane.registerKeyboardAction(this::onCancel,
+ KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ }
+
+ /**
+ * EFFECTS: Render the form.
+ */
+ private JPanel renderForm() {
+ final JPanel panelForm = new JPanel(new GridBagLayout());
+ panelForm.add(new JLabel("Template: "), new GCBuilder().anchor(WEST).build());
+ panelForm.add(new JLabel("Subject: "), new GCBuilder().gridY(1).anchor(WEST).build());
+ panelForm.add(new JLabel("Validity (Days): "), new GCBuilder().gridY(2).anchor(WEST).build());
+ panelForm.add(createTemplateComponent(), new GCBuilder().gridXY(1, 0).anchor(WEST)
+ .fill(HORIZONTAL).build());
+ panelForm.add(textFieldSubject, new GCBuilder().gridXY(1, 1).anchor(WEST).fill(HORIZONTAL).build());
+ panelForm.add(spinnerValidity, new GCBuilder().gridXY(1, 2).anchor(WEST).fill(HORIZONTAL).build());
+ panelForm.add(new JPanel(), new GCBuilder().gridXY(1, 3).expandXY().fill(HORIZONTAL).build());
+ return panelForm;
+ }
+
+ /**
+ * EFFECTS: Create the component for subject.
+ */
+ protected abstract JComponent createTemplateComponent();
+
+ /**
+ * EFFECTS: Handle OK.
+ */
+ protected abstract void onOK(ActionEvent ev);
+
+ /**
+ * EFFECTS: Handle cancel: clear result and close dialog.
+ * MODIFIES: this
+ */
+ private void onCancel(ActionEvent ev) {
+ res = null;
+ dispose();
+ }
+
+ /**
+ * EFFECTS: Get the result.
+ */
+ public T getRes() {
+ return res;
+ }
+}
diff --git a/src/main/ui/widgets/CertTableModel.java b/src/main/ui/widgets/CertTableModel.java
new file mode 100644
index 0000000..0259e1b
--- /dev/null
+++ b/src/main/ui/widgets/CertTableModel.java
@@ -0,0 +1,129 @@
+package ui.widgets;
+
+import model.ca.CertificationAuthority;
+import model.pki.cert.Certificate;
+import model.pki.crl.RevokedCertificate;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+/**
+ * Table model that displays issued certificates.
+ */
+public class CertTableModel extends AbstractTableModel {
+ /**
+ * Valid certificate icon.
+ */
+ private static final ImageIcon ICON_OK =
+ new ImageIcon(CertTableModel.class.getResource("/verified.png"));
+
+ /**
+ * Revoked certificate icon (same as toolbar revoke icon).
+ */
+ private static final ImageIcon ICON_REVOKED =
+ new ImageIcon(CertTableModel.class.getResource("/deletetest.png"));
+
+ /**
+ * Columns
+ */
+ private static final String[] COLS = new String[] {
+ "", // Icon
+ "Serial",
+ "Subject",
+ "Signed",
+ "Expires"
+ };
+
+ /**
+ * Pointer to {@link CertificationAuthority#getSigned()}
+ */
+ private List<Certificate> ptrData;
+
+ /**
+ * Pointer to {@link CertificationAuthority#getRevoked()}
+ */
+ private List<RevokedCertificate> ptrRevokedData;
+
+ /**
+ * EFFECTS: Set pointer to certs
+ * MODIFIES: this
+ */
+ public void setPtrData(List<Certificate> ptrData) {
+ this.ptrData = ptrData;
+ }
+
+ /**
+ * EFFECTS: Set pointer to revoked
+ * MODIFIES: this
+ */
+ public void setPtrRevokedData(List<RevokedCertificate> ptrRevokedData) {
+ this.ptrRevokedData = ptrRevokedData;
+ }
+
+ /**
+ * EFFECTS: Count rows.
+ */
+ @Override
+ public int getRowCount() {
+ return ptrData == null ? 0 : ptrData.size();
+ }
+
+ /**
+ * EFFECTS: Count columns.
+ */
+ @Override
+ public int getColumnCount() {
+ return COLS.length;
+ }
+
+ /**
+ * EFFECTS: Get column name.
+ * REQUIRES: column in [9, getColumnCount())
+ */
+ @Override
+ public String getColumnName(int column) {
+ return COLS[column];
+ }
+
+ /**
+ * EFFECTS: Return the value for a cell:
+ * ImageIcon (Valid / Revoked)
+ * String (Serial number)
+ * String (Subject)
+ * String (NotBefore)
+ * String (NotAfter)
+ * Throws {@link IllegalArgumentException} if columnIndex is not in 0 ~ 4
+ * REQUIRES: rowIndex must in range.
+ */
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ final Certificate e = ptrData.get(rowIndex);
+ switch (columnIndex) {
+ case 0: return ptrRevokedData.stream().anyMatch(r ->
+ r.getSerialNumber().getLong() == e.getCertificate().getSerialNumber().getLong()
+ ) ? ICON_REVOKED : ICON_OK;
+ case 1: return e.getCertificate().getSerialNumber().getLong();
+ case 2: return e.getCertificate().getSubject().toString();
+ case 3:
+ return e.getCertificate().getValidity().getNotBefore().getTimestamp()
+ .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+ case 4:
+ return e.getCertificate().getValidity().getNotAfter().getTimestamp()
+ .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+ default: throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * EFFECTS: Return ImageIcon for 0, String otherwise.
+ */
+ @Override
+ public Class<?> getColumnClass(int columnIndex) {
+ switch (columnIndex) {
+ case 0: return ImageIcon.class;
+ default: return String.class;
+ }
+ }
+}
diff --git a/src/main/ui/widgets/GCBuilder.java b/src/main/ui/widgets/GCBuilder.java
new file mode 100644
index 0000000..0590cc1
--- /dev/null
+++ b/src/main/ui/widgets/GCBuilder.java
@@ -0,0 +1,213 @@
+package ui.widgets;
+
+import java.awt.*;
+
+/**
+ * A builder for {@link GridBagConstraints}.
+ */
+public class GCBuilder {
+ /**
+ * Grix X and Y, defaults to {@link GridBagConstraints#RELATIVE}
+ */
+ private int gridX = GridBagConstraints.RELATIVE;
+
+ private int gridY = GridBagConstraints.RELATIVE;
+
+ /**
+ * Weight X and Y, [0.0, 1.0], defaults to 0.0
+ */
+ private double weightX = 0.0;
+
+ private double weightY = 0.0;
+
+ /**
+ * Anchor, defaults to {@link GridBagConstraints#CENTER}
+ */
+ private int anchor = GridBagConstraints.CENTER;
+
+ /**
+ * How to stretch the component, defaults to {@link GridBagConstraints#NONE}
+ */
+ private int fill = GridBagConstraints.NONE;
+
+ /**
+ * Insects in pixels.
+ */
+ private int insectTop = 0;
+
+ private int insectLeft = 0;
+
+ private int insectRight = 0;
+
+ private int insectBottom = 0;
+
+ /**
+ * EFFECTS: Build the {@link GridBagConstraints} based on parameters.
+ * Grid width, grid height, ipad X, ipad Y will be 1, 1, 0, 0.
+ */
+ public GridBagConstraints build() {
+ return new GridBagConstraints(gridX, gridY,
+ 1, 1,
+ weightX, weightY,
+ anchor, fill,
+ new Insets(insectTop, insectLeft, insectBottom, insectRight),
+ 0, 0);
+ }
+
+ /**
+ * EFFECTS: Set grid X and Y.
+ * REQUIRES: > 0
+ * MODIFIES: this
+ */
+ public GCBuilder gridXY(int gridX, int gridY) {
+ return this.gridX(gridX).gridY(gridY);
+ }
+
+ /**
+ * EFFECTS: Set grid X.
+ * REQUIRES: > 0
+ * MODIFIES: this
+ */
+ public GCBuilder gridX(int gridX) {
+ this.gridX = gridX;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set grid Y.
+ * REQUIRES: > 0
+ * MODIFIES: this
+ */
+ public GCBuilder gridY(int gridY) {
+ this.gridY = gridY;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set weight X and Y.
+ * REQUIRES: [0.0, 1.0]
+ * MODIFIES: this
+ */
+ public GCBuilder weightXY(double weightX, double weightY) {
+ return this.weightX(weightX).weightY(weightY);
+ }
+
+ /**
+ * EFFECTS: Set weight X and Y to 1.0
+ * MODIFIES: this
+ */
+ public GCBuilder expandXY() {
+ return this.expandX().expandY();
+ }
+
+ /**
+ * EFFECTS: Set weight X to 1.0
+ * MODIFIES: this
+ */
+ public GCBuilder expandX() {
+ return this.weightX(1.0);
+ }
+
+ /**
+ * EFFECTS: Set weight X to 1.0
+ * MODIFIES: this
+ */
+ public GCBuilder weightX(double weightX) {
+ this.weightX = weightX;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set weight Y to 1.0
+ * MODIFIES: this
+ */
+ public GCBuilder expandY() {
+ return this.weightY(1.0);
+ }
+
+ /**
+ * EFFECTS: Set weight X to 1.0
+ * MODIFIES: this
+ */
+ public GCBuilder weightY(double weightY) {
+ this.weightY = weightY;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set anchor.
+ * REQUIRES: anchor in {@link GridBagConstraints} constants.
+ * MODIFIES: this
+ */
+ public GCBuilder anchor(int anchor) {
+ this.anchor = anchor;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set fill.
+ * REQUIRES: fill in {@link GridBagConstraints} constants.
+ * MODIFIES: this
+ */
+ public GCBuilder fill(int fill) {
+ this.fill = fill;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set insects to be 8, 8, 8, 8
+ * MODIFIES: this
+ */
+ public GCBuilder marginInsects() {
+ return this.insects(8, 8, 8, 8);
+ }
+
+ /**
+ * EFFECTS: Set insects.
+ * REQUIRES: >= 0
+ * MODIFIES: this
+ */
+ public GCBuilder insects(int top, int left, int bottom, int right) {
+ return this.insectTop(top).insectLeft(left).insectBottom(bottom).insectRight(right);
+ }
+
+ /**
+ * EFFECTS: Set top insect
+ * REQUIRES: >= 0
+ * MODIFIES: this
+ */
+ public GCBuilder insectTop(int insectTop) {
+ this.insectTop = insectTop;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set left insect
+ * REQUIRES: >= 0
+ * MODIFIES: this
+ */
+ public GCBuilder insectLeft(int insectLeft) {
+ this.insectLeft = insectLeft;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set right insect
+ * REQUIRES: >= 0
+ * MODIFIES: this
+ */
+ public GCBuilder insectRight(int insectRight) {
+ this.insectRight = insectRight;
+ return this;
+ }
+
+ /**
+ * EFFECTS: Set bottom insect
+ * REQUIRES: >= 0
+ * MODIFIES: this
+ */
+ public GCBuilder insectBottom(int insectBottom) {
+ this.insectBottom = insectBottom;
+ return this;
+ }
+}
diff --git a/src/main/ui/widgets/LogTableModel.java b/src/main/ui/widgets/LogTableModel.java
new file mode 100644
index 0000000..dc7dcbd
--- /dev/null
+++ b/src/main/ui/widgets/LogTableModel.java
@@ -0,0 +1,79 @@
+package ui.widgets;
+
+import model.ca.AuditLogEntry;
+import model.ca.CertificationAuthority;
+
+import javax.swing.table.AbstractTableModel;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+/**
+ * Table model that displays audit logs.
+ */
+public class LogTableModel extends AbstractTableModel {
+ /**
+ * Columns
+ */
+ private static final String[] COLS = new String[] {
+ "Time",
+ "Operator",
+ "Action"
+ };
+
+ /**
+ * Pointer to the {@link CertificationAuthority#getLogs()}.
+ */
+ private List<AuditLogEntry> ptrData;
+
+ /**
+ * EFFECTS: Set the pointer to templates
+ * MODIFIES: this
+ */
+ public void setPtrData(List<AuditLogEntry> ptrData) {
+ this.ptrData = ptrData;
+ }
+
+ /**
+ * EFFECT: Return number of rows.
+ */
+ @Override
+ public int getRowCount() {
+ return ptrData == null ? 0 : ptrData.size();
+ }
+
+ /**
+ * EFFECT: Return number of columns.
+ */
+ @Override
+ public int getColumnCount() {
+ return COLS.length;
+ }
+
+ /**
+ * EFFECTS: Get column name.
+ * REQUIRES: column in [9, getColumnCount())
+ */
+ @Override
+ public String getColumnName(int column) {
+ return COLS[column];
+ }
+
+ /**
+ * EFFECTS: Return the value for a cell:
+ * String (Time)
+ * String (Operator)
+ * String (Action)
+ * Throws {@link IllegalArgumentException} if columnIndex is not in 0 ~ 2
+ * REQUIRES: rowIndex must in range.
+ */
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ final AuditLogEntry e = ptrData.get(rowIndex);
+ switch (columnIndex) {
+ case 0: return e.getTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+ case 1: return e.getUser();
+ case 2: return e.getAction();
+ default: throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/src/main/ui/widgets/TemplateTableModel.java b/src/main/ui/widgets/TemplateTableModel.java
new file mode 100644
index 0000000..da4557f
--- /dev/null
+++ b/src/main/ui/widgets/TemplateTableModel.java
@@ -0,0 +1,105 @@
+package ui.widgets;
+
+import model.ca.CertificationAuthority;
+import model.ca.Template;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import java.util.List;
+
+/**
+ * Table model that displays templates.
+ */
+public class TemplateTableModel extends AbstractTableModel {
+ /**
+ * Template enabled icon, same as toolbar enable icon.
+ */
+ private static final ImageIcon ICON_ENABLED =
+ new ImageIcon(TemplateTableModel.class.getResource("/enable.png"));
+
+ /**
+ * Template disbled icon, same as toolbar enable icon.
+ */
+ private static final ImageIcon ICON_DISABLED =
+ new ImageIcon(TemplateTableModel.class.getResource("/disable.png"));
+
+ /**
+ * Columns
+ */
+ private static final String[] COLS = new String[] {
+ "", // Icon
+ "Name",
+ "Subject",
+ "Validity"
+ };
+
+ /**
+ * Pointer to the {@link CertificationAuthority#getTemplates()}.
+ */
+ private List<Template> ptrData;
+
+ /**
+ * EFFECTS: Set the pointer to templates
+ * MODIFIES: this
+ */
+ public void setPtrData(List<Template> ptrData) {
+ this.ptrData = ptrData;
+ }
+
+ /**
+ * EFFECT: Return number of rows.
+ * REQUIRES: column in [9, getColumnCount())
+ */
+ @Override
+ public int getRowCount() {
+ return ptrData == null ? 0 : ptrData.size();
+ }
+
+ /**
+ * EFFECT: Return number of rows.
+ */
+ @Override
+ public int getColumnCount() {
+ return COLS.length;
+ }
+
+ /**
+ * EFFECTS: Get column name.
+ */
+ @Override
+ public String getColumnName(int column) {
+ return COLS[column];
+ }
+
+ /**
+ * EFFECTS: Return the value for a cell:
+ * ImageIcon (Enabled / Disabled)
+ * String (Name)
+ * String (Subject or Not Set)
+ * String (xx days)
+ * Throws {@link IllegalArgumentException} if columnIndex is not in 0 ~ 3
+ * REQUIRES: rowIndex must in range.
+ */
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ final Template e = ptrData.get(rowIndex);
+ switch (columnIndex) {
+ case 0: return e.isEnabled() ? ICON_ENABLED : ICON_DISABLED;
+ case 1: return e.getName();
+ case 2: return e.getSubject() == null ? "<Not Set>" : e.getSubject().toString();
+ case 3: return String.format("%d days", e.getValidity());
+ default: throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * EFFECTS: Return ImageIcon for 0, String otherwise.
+ */
+ @Override
+ public Class<?> getColumnClass(int columnIndex) {
+ switch (columnIndex) {
+ case 0: return ImageIcon.class;
+ default: return String.class;
+ }
+ }
+}
diff --git a/src/main/ui/widgets/UIUtils.java b/src/main/ui/widgets/UIUtils.java
new file mode 100644
index 0000000..4442be3
--- /dev/null
+++ b/src/main/ui/widgets/UIUtils.java
@@ -0,0 +1,168 @@
+package ui.widgets;
+
+import model.asn1.exceptions.ParseException;
+import ui.Utils;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.stream.IntStream;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static javax.swing.JOptionPane.*;
+
+/**
+ * Useful utilities for building GUI.
+ */
+public class UIUtils {
+ /**
+ * EFFECTS: Create a horizontal actions pane:
+ * -----------------------------------------------------------
+ * | | Button1 | Button2 | Button3 | ButtonN |
+ * -----------------------------------------------------------
+ * REQUIRES: buttons != null
+ */
+ public static JPanel createActionsPane(JButton... buttons) {
+ final JPanel panelAct = new JPanel();
+ panelAct.setLayout(new GridBagLayout());
+ IntStream.range(0, buttons.length)
+ .forEach(i -> panelAct.add(buttons[i],
+ new GCBuilder().gridXY(i + 1, 1).fill(HORIZONTAL).build()));
+ panelAct.add(new JPanel(), new GCBuilder().expandXY().fill(BOTH).build());
+ return panelAct;
+ }
+
+ /**
+ * EFFECTS: Show / hide default text for a card layout container.
+ * MODIFIES: cardLayoutPanel
+ * REQUIRES: cardLayoutPanel must have a card layout; it must have CardContent and CardDefault cards.
+ */
+ public static void setContentVisible(Container cardLayoutPanel, boolean showContent) {
+ switchTo(cardLayoutPanel, showContent ? "CardContent" : "CardDefault");
+ }
+
+ /**
+ * EFFECTS: Switch to the card for a card layout panel.
+ * MODIFIES: cardLayoutPanel
+ * REQUIRES: cardLayoutPanel must have a card layout; it must have "card" card.
+ */
+ public static void switchTo(Container cardLayoutPanel, String card) {
+ ((CardLayout) cardLayoutPanel.getLayout()).show(cardLayoutPanel, card);
+ }
+
+ /**
+ * EFFECTS: Show an error message based on {@link Throwable#getMessage()}
+ * REQUIRES: component must have a frame.
+ */
+ public static void alert(Component component, String title, Throwable e) {
+ alert(component, title, e.getMessage());
+ }
+
+ /**
+ * EFFECTS: Show an error message.
+ * REQUIRES: component must have a frame.
+ */
+ public static void alert(Component component, String title, String message) {
+ showMessageDialog(getFrameForComponent(component),
+ message,
+ title,
+ ERROR_MESSAGE);
+ }
+
+ /**
+ * EFFECTS: Show a file chooser with the given filter title and list of extensions. Starting from cwd.
+ * Return null if cancelled.
+ */
+ public static Path chooseFile(Component component, String filterTitle, String... extensions) {
+ final JFileChooser fc = new JFileChooser();
+ fc.setFileFilter(new FileNameExtensionFilter(filterTitle, extensions));
+ fc.setCurrentDirectory(Paths.get("").toAbsolutePath().toFile());
+ if (fc.showOpenDialog(getFrameForComponent(component)) == JFileChooser.APPROVE_OPTION) {
+ return fc.getSelectedFile().toPath();
+ }
+ return null;
+ }
+
+ /**
+ * EFFECTS: Create a JPanel with CardLayout that has CardDefault set to component and CardContent set to a JLabel
+ * with the default text.
+ */
+ public static JPanel defView(Component component, String defaultText) {
+ final JPanel panel = new JPanel();
+ panel.setLayout(new CardLayout(0, 0));
+
+ JLabel labelDefault = new JLabel(defaultText);
+ labelDefault.setHorizontalAlignment(0);
+ panel.add(labelDefault, "CardDefault");
+ panel.add(component, "CardContent");
+
+ return panel;
+ }
+
+ /**
+ * EFFECTS: Create a JScrollPane-wrapped JTable.
+ * MODIFIES: table
+ */
+ public static JScrollPane scrTbl(JTable table) {
+ final JScrollPane scrollPane = new JScrollPane();
+ table.setFillsViewportHeight(true);
+ scrollPane.setViewportView(table);
+ return scrollPane;
+ }
+
+ /**
+ * EFFECTS: Parse the given path and automatically determine if it is a DER binary or a PEM. Automatically decode
+ * PEM.
+ * Throws {@link IOException} if it cannot be read.
+ * Throws {@link ParseException} if the PEM is invalid.
+ */
+ public static Byte[] openDERorPEM(Path path, String tag) throws IOException, ParseException {
+ final InputStream fd = Files.newInputStream(path, StandardOpenOption.READ);
+ Byte[] bs = Utils.byteToByte(fd.readAllBytes());
+ fd.close();
+ if (bs.length < 1) {
+ throw new ParseException("Invalid file: too short");
+ }
+ if (bs[0] == '-') {
+ bs = Utils.parsePEM(bs, tag);
+ }
+ return bs;
+ }
+
+ /**
+ * EFFECTS: Create a button with the given label and click listener.
+ */
+ public static JButton btn(String string, ActionListener onClick) {
+ final JButton btn = new JButton(string);
+ btn.addActionListener(onClick);
+ return btn;
+ }
+
+ /**
+ * EFFECTS: Create a button with the given label, mnemonic, and click listener.
+ */
+ public static JButton btn(String string, char m, ActionListener onClick) {
+ final JButton btn = new JButton(string);
+ btn.setMnemonic(m);
+ btn.setDisplayedMnemonicIndex(0);
+ btn.addActionListener(onClick);
+ return btn;
+ }
+
+ /**
+ * EFFECTS: Create a button with the given label, mnemonic, icon, and click listener.
+ */
+ public static JButton btn(String string, String icon, ActionListener onClick) {
+ final JButton btn = new JButton(string, new ImageIcon(UIUtils.class.getResource("/" + icon)));
+ btn.addActionListener(onClick);
+ return btn;
+ }
+}