package chatty.gui.components.srl; import chatty.gui.HtmlColors; import chatty.gui.TwitchUrl; import chatty.gui.UrlOpener; import chatty.gui.components.menus.ContextMenu; import chatty.gui.components.menus.ContextMenuAdapter; import chatty.gui.components.menus.ContextMenuListener; import chatty.gui.components.menus.RaceContextMenu; import chatty.gui.components.settings.ListTableModel; import chatty.util.DateTime; import chatty.util.srl.Race; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.List; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.Timer; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableRowSorter; /** * Table to display a list of SRL Races. When a race is supposed to be opened * (from the context-menu or by double-clicking on it), it sends that request * back to the listener. * * @author tduva */ public class RacesTable extends JTable { private static final int UPDATE_DELAY = 30*1000; private final RacesTableListener listener; private final MyTableModel races = new MyTableModel(); private final ContextMenuListener contextMenuListener; public RacesTable(RacesTableListener listener) { this.listener = listener; contextMenuListener = new MyContextMenuListener(); addMouseListener(new MyMouseListener()); setModel(races); setRowSorter(new MySorter(races)); setFillsViewportHeight(true); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); getTableHeader().setReorderingAllowed(false); getColumnModel().getColumn(2).setMaxWidth(100); getColumnModel().getColumn(3).setMaxWidth(50); getColumnModel().getColumn(4).setMaxWidth(40); getColumnModel().getColumn(2).setCellRenderer(new StateRenderer()); Timer timer = new Timer(UPDATE_DELAY, new Updater()); timer.setRepeats(true); timer.start(); } public void setData(Collection<Race> data) { races.setData(data); } public Race getSelectedRace() { int selected = getSelectedRow(); if (selected == -1) { return null; } return races.get(convertRowIndexToModel(selected)); } private class Updater implements ActionListener { @Override public void actionPerformed(ActionEvent e) { races.updateTimes(); } } private class MyMouseListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { Race selectedRace = getSelectedRace(); if (selectedRace != null) { listener.openRace(selectedRace); } } } @Override public void mousePressed(MouseEvent e) { selectClicked(e); openContextMenu(e); } @Override public void mouseReleased(MouseEvent e) { openContextMenu(e); } private void openContextMenu(MouseEvent e) { if (e.isPopupTrigger()) { Race selectedRace = getSelectedRace(); if (selectedRace != null) { ContextMenu m = new RaceContextMenu(selectedRace, contextMenuListener, false); m.show(e.getComponent(), e.getX(), e.getY()); } } } private void selectClicked(MouseEvent e) { int clickedRow = rowAtPoint(e.getPoint()); if (clickedRow != -1) { setRowSelectionInterval(clickedRow, clickedRow); } } } private static class MyTableModel extends ListTableModel<Race> { public MyTableModel() { super(new String[]{"Game","Goal","State","Time","Entrants"}); } @Override public Object getValueAt(int rowIndex, int columnIndex) { Race r = get(rowIndex); switch (columnIndex) { case 0: return r.game; case 2: return r.statetext; case 1: return r.goal; case 4: return r.getEntrants().size(); case 3: return formatTime(r.time); } return null; } @Override public Class getColumnClass(int c) { if (c == 4) { return Integer.class; } else { return String.class; } } private String formatTime(long time) { if (time > 0) { return DateTime.agoClock(time, false); } return "-"; } public void updateTimes() { for (int i=0;i<getRowCount();i++) { fireTableCellUpdated(i, 3); } } } private static class MySorter extends TableRowSorter<MyTableModel> { public MySorter(MyTableModel model) { super(model); } /** * @inherited * <p> */ @Override public void toggleSortOrder(int column) { List<? extends RowSorter.SortKey> sortKeys = getSortKeys(); if (sortKeys.size() > 0) { if (sortKeys.get(0).getSortOrder() == SortOrder.DESCENDING) { setSortKeys(null); return; } } super.toggleSortOrder(column); } } private static class StateRenderer extends DefaultTableCellRenderer { private static final Color OPEN = new Color(60,200,10); private static final Color ENDED = Color.RED; private static final Color COMPLETE = HtmlColors.getNamedColor("BlueViolet"); @Override public void setValue(Object value) { if (value == null) { return; } String state = (String)value; if (state.equals("Entry Open")) { setForeground(OPEN); } else if (state.equals("Race ended")) { setForeground(ENDED); } else if (state.equals("Complete") || state.equals("Race Over")) { setForeground(COMPLETE); } else { setForeground(Color.BLACK); } setText(state); } } private class MyContextMenuListener extends ContextMenuAdapter { @Override public void menuItemClicked(ActionEvent e) { Race selectedRace = getSelectedRace(); if (selectedRace == null) { return; } if (e.getActionCommand().equals("raceInfo")) { listener.openRace(selectedRace); } else if (e.getActionCommand().equals("srlRacePage")) { String url = TwitchUrl.makeSrlRaceLink(selectedRace.id); UrlOpener.openUrlPrompt(RacesTable.this, url, true); } else if (e.getActionCommand().equals("speedruntv")) { String url = TwitchUrl.makeSrtRaceLink(selectedRace.id); UrlOpener.openUrlPrompt(RacesTable.this, url, true); } else if (e.getActionCommand().equals("joinSrlChannel")) { String url = TwitchUrl.makeSrlIrcLink(selectedRace.id); UrlOpener.openUrlPrompt(RacesTable.this, url, true); } } } public static interface RacesTableListener { void openRace(Race race); } }