diff --git a/build.gradle b/build.gradle index 2ef5baa..9800faf 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ dependencies { compile fileTree(dir: 'lib', include: ['*.jar']) compile 'com.1stleg:jnativehook:2.0.2' compile 'com.thoughtworks.xstream:xstream:1.4.4' + compile group: 'org.json', name: 'json', version: '20180130' } macAppBundle { diff --git a/src/main/java/org/fenix/WorldRecord/Category.java b/src/main/java/org/fenix/WorldRecord/Category.java new file mode 100644 index 0000000..269c9e8 --- /dev/null +++ b/src/main/java/org/fenix/WorldRecord/Category.java @@ -0,0 +1,32 @@ +package org.fenix.WorldRecord; + +/** + * Category class to represent a speedrun.com category + * @author 4ilo 2018 + */ +public class Category +{ + private String name = ""; + private String id = ""; + + public Category(String name, String id) + { + this.name = name; + this.id = id; + } + + public String getName() + { + return name; + } + + public String getId() + { + return id; + } + + public String toString() + { + return this.name; + } +} diff --git a/src/main/java/org/fenix/WorldRecord/Game.java b/src/main/java/org/fenix/WorldRecord/Game.java new file mode 100644 index 0000000..6c42749 --- /dev/null +++ b/src/main/java/org/fenix/WorldRecord/Game.java @@ -0,0 +1,32 @@ +package org.fenix.WorldRecord; + +/** + * Game class to represent a speedrun.com game + * @author 4ilo 2018 + */ +public class Game +{ + private String title; + private String id; + + public Game(String title, String id) + { + this.title = title; + this.id = id; + } + + public String getTitle() + { + return title; + } + + public String getId() + { + return id; + } + + public String toString() + { + return this.title; + } +} diff --git a/src/main/java/org/fenix/WorldRecord/JSONReader.java b/src/main/java/org/fenix/WorldRecord/JSONReader.java new file mode 100644 index 0000000..67c8c31 --- /dev/null +++ b/src/main/java/org/fenix/WorldRecord/JSONReader.java @@ -0,0 +1,58 @@ +package org.fenix.WorldRecord; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.*; +import java.net.URL; +import java.nio.charset.Charset; + +/** + * JSONReader class to fetch JSON from api + * @author 4ilo 2018 + */ +public class JSONReader +{ + /** + * Get the content of the given reader as a string + * @param reader a reader object + * @return String with the data of the reader + * @throws IOException + */ + private static String readAll(Reader reader) throws IOException + { + StringBuilder builder = new StringBuilder(); + int cp; + + while((cp = reader.read()) != -1) + { + builder.append((char) cp); + } + + return builder.toString(); + } + + /** + * Read the json from the given json-api url + * @param url the api url + * @return JSONObject with the data from the url + * @throws IOException + * @throws JSONException + */ + public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException + { + InputStream stream = new URL(url).openStream(); + + try + { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8"))); + String jsonText = readAll(reader); + + return new JSONObject(jsonText); + + } finally + { + stream.close(); + } + } +} diff --git a/src/main/java/org/fenix/WorldRecord/RecordDialog.java b/src/main/java/org/fenix/WorldRecord/RecordDialog.java new file mode 100644 index 0000000..f00c25b --- /dev/null +++ b/src/main/java/org/fenix/WorldRecord/RecordDialog.java @@ -0,0 +1,299 @@ +package org.fenix.WorldRecord; + +import org.fenix.llanfair.dialog.*; + +import java.awt.*; +import javax.swing.*; +import java.awt.event.*; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Dialog window to select a world record on speedrun.com + * @author 4ilo 2018 + */ +public class RecordDialog extends JDialog +{ + private JLabel searchLabel = new JLabel("Search game:"); + private JButton searchButton = new JButton("Search"); + private JTextField searchField = new JTextField(); + + private DefaultComboBoxModel gameListModel = new DefaultComboBoxModel<>(); + private JComboBox games = new JComboBox<>(gameListModel); + private JLabel gamesLabel = new JLabel("Games:"); + + private DefaultComboBoxModel categoryListModel = new DefaultComboBoxModel<>(); + private JComboBox categories = new JComboBox<>(categoryListModel); + private JLabel categoriesLabel = new JLabel("Categories:"); + + private JButton close = new JButton("Close"); + private JButton ok = new JButton("Ok"); + private JLabel worldRecord = new JLabel("Unknown World Record"); + + private ActionListener categoryListener; + private ActionListener gameListener; + + private Category category; + private EditRun editRun; + + + public RecordDialog(EditRun editRun) + { + this.editRun = editRun; + + JPanel searchPanel = new JPanel(new FlowLayout()); + { + searchField.setPreferredSize(new Dimension(200,30)); + + searchPanel.add(searchLabel); + searchPanel.add(searchField); + searchPanel.add(searchButton); + + searchButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + searchGame(searchField.getText()); + } + }); + } + JPanel gamesPanel = new JPanel(new GridLayout(1,2)); + { + gamesPanel.add(gamesLabel); + gamesPanel.add(games); + + games.setEnabled(false); + } + JPanel categoriesPanel = new JPanel(new GridLayout(1,2)); + { + categoriesPanel.add(categoriesLabel); + categoriesPanel.add(categories); + + categories.setEnabled(false); + } + JPanel buttonPanel = new JPanel(new FlowLayout()); + { + buttonPanel.add(ok); + buttonPanel.add(close); + + ok.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + actionOk(); + } + }); + + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + close(); + } + }); + } + + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)); + this.add(searchPanel); + this.add(gamesPanel); + this.add(categoriesPanel); + + this.add(worldRecord); + worldRecord.setAlignmentX(Component.CENTER_ALIGNMENT); + + this.add(buttonPanel); + + this.setTitle("World Record selection"); + pack(); + } + + /** + * Search the game title on speedrun.com + * @param name The name of the game + */ + private void searchGame(String name) + { + this.resetFields(); + + ArrayList games = new ArrayList<>(); + + try { + games = WorldRecordParser.searchGames(name); + } catch (IOException e) + { + showError(); + } + + this.setGames(games); + this.addGameListener(); + } + + /** + * Get the categories for a game on speedrun.com + * @param game A game object received from the game search + */ + private void getCategories(Game game) + { + ArrayList categories = new ArrayList<>(); + + try { + categories = WorldRecordParser.getCategories(game); + } catch (IOException e) + { + showError(); + } + + this.setCategories(categories); + this.addCategoryListener(); + } + + /** + * Get the world record time and owner from speedrun.com + * @param category A category object received from the category search + */ + private void getWorldRecord(Category category) + { + String worldRecord = ""; + + try { + worldRecord = WorldRecordParser.getRecord(category); + } catch (IOException e) + { + showError(); + } + + this.worldRecord.setText(worldRecord); + this.category = category; + } + + /** + * Append the games in the array to the games combobox + * @param games A ArrayList of game objects + */ + private void setGames(ArrayList games) + { + this.resetFields(); + + for(Game game: games) + { + gameListModel.addElement(game); + } + + this.games.setEnabled(true); + } + + /** + * Append the categories in the array to the categories checkbox + * @param categories A ArrayList of Category objects + */ + private void setCategories(ArrayList categories) + { + categoryListModel.removeAllElements(); + + for(Category category: categories) + { + categoryListModel.addElement(category); + } + + this.categories.setEnabled(true); + } + + /** + * Close the dialog without saving + */ + private void close() + { + this.setVisible(false); + } + + /** + * Close the dialog and send a signal to the parent window + */ + private void actionOk() + { + this.category = ((Category) categories.getSelectedItem()); + this.setVisible(false); + + this.editRun.recordSet(); + } + + /** + * Add the changeListener to the categories combobox + */ + private void addCategoryListener() + { + categoryListener = new ActionListener() { + public void actionPerformed(ActionEvent e) + { + getWorldRecord((Category) categories.getSelectedItem()); + } + }; + + categories.addActionListener(categoryListener); + } + + /** + * Add the changeListener to the games combobox + */ + private void addGameListener() + { + + gameListener = new ActionListener() { + public void actionPerformed(ActionEvent e) + { + getCategories((Game) games.getSelectedItem()); + } + }; + + games.addActionListener(gameListener); + } + + /** + * Reset the comboboxes + */ + private void resetFields() + { + try { + categories.removeActionListener(categoryListener); + games.removeActionListener(gameListener); + } catch (Exception e){} + + categories.setEnabled(false); + games.setEnabled(false); + + gameListModel.removeAllElements(); + categoryListModel.removeAllElements(); + } + + public static void showError() + { + JOptionPane.showMessageDialog(null, "Could not connect to speedrun.com", + "Error", JOptionPane.ERROR_MESSAGE); + } + + /** + * Show the dialog + */ + public void display() + { + setAlwaysOnTop(true); + setModalityType(ModalityType.APPLICATION_MODAL); + pack(); + setVisible(true); + } + + /** + * Get the selected category object + * @return Category + */ + public Category getCategory() + { + return category; + } + + /** + * Get the selected record string + * @return String + */ + public String getRecordString() + { + return worldRecord.getText(); + } +} diff --git a/src/main/java/org/fenix/WorldRecord/WorldRecordParser.java b/src/main/java/org/fenix/WorldRecord/WorldRecordParser.java new file mode 100644 index 0000000..0f4d968 --- /dev/null +++ b/src/main/java/org/fenix/WorldRecord/WorldRecordParser.java @@ -0,0 +1,132 @@ +package org.fenix.WorldRecord; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * @author 4ilo 2018 + */ +public class WorldRecordParser +{ + /** + * Search the speedrun.com database for the game with the given name + * @param name The name of the game you want to search + * @return List of games + * @throws IOException + */ + public static ArrayList searchGames(String name) throws IOException + { + String url = "https://www.speedrun.com/api/v1/games?name=" + name; + JSONArray json_games; + ArrayList games = new ArrayList<>(); + + + JSONObject json = JSONReader.readJsonFromUrl(url); + json_games = json.getJSONArray("data"); + + + for(Object game: json_games) + { + JSONObject obj = (JSONObject) game; + games.add(new Game( + obj.getJSONObject("names").get("international").toString(), + obj.get("id").toString() + )); + } + + return games; + } + + /** + * Get the speedrun.com categories for the given game + * @param game WorldRecord.Game object received from a game search + * @return List of categories + * @throws IOException + */ + public static ArrayList getCategories(Game game) throws IOException + { + String url = "https://www.speedrun.com/api/v1/games/" + game.getId() + "/categories"; + + ArrayList categories = new ArrayList<>(); + + + JSONObject json = JSONReader.readJsonFromUrl(url); + JSONArray json_categories = json.getJSONArray("data"); + + for(Object category: json_categories) + { + JSONObject obj = (JSONObject) category; + categories.add(new Category( + obj.get("name").toString(), + obj.get("id").toString() + )); + } + + return categories; + } + + /** + * Get the world record string for the given speedrun.com category + * @param category WorldRecord.Category object received from a category search + * @return The world record string + * @throws IOException + */ + public static String getRecord(Category category) throws IOException + { + String url = "https://www.speedrun.com/api/v1/categories/" + category.getId() + "/records"; + + JSONObject json = JSONReader.readJsonFromUrl(url); + JSONArray json_runs = json.getJSONArray("data").getJSONObject(0).getJSONArray("runs"); + + JSONObject wr_run = json_runs.getJSONObject(0).getJSONObject("run"); + + String player_name = getPlayerName(wr_run.getJSONArray("players").getJSONObject(0)); + + return "World record: " + parseTime(wr_run.getJSONObject("times").getFloat("primary_t")) + " by " + player_name; + } + + /** + * Get the speedrun.com player name for the given player JSONObject + * @param player The player JSONObject extracted from a run object + * @return The players name + * @throws IOException + */ + private static String getPlayerName(JSONObject player) throws IOException + { + String uri = player.get("uri").toString(); + JSONObject json = JSONReader.readJsonFromUrl(uri); + return json.getJSONObject("data").getJSONObject("names").get("international").toString(); + } + + /** + * Parse a time in seconds and miliseconds to a HH:MM:SS.sss format + * @param time_seconds the time in seconds + * @return Time in HH:MM:SS.sss format + */ + private static String parseTime(float time_seconds) + { + int hours = (int) time_seconds / 3600; + int secLeft = (int) time_seconds - hours*3600; + + int minutes = secLeft / 60; + int seconds = secLeft - minutes * 60; + + String time = ""; + + if(hours != 0) + time += String.format("%02d:", hours); + + time += String.format("%02d:%02d", minutes, seconds); + + if(time_seconds % 1 != 0) + { + int miliseconds = Math.round((time_seconds%1) * 1000); + time += "." + miliseconds; + } + + return time; + } +} diff --git a/src/main/java/org/fenix/llanfair/Language.java b/src/main/java/org/fenix/llanfair/Language.java index c28a32d..770fd39 100644 --- a/src/main/java/org/fenix/llanfair/Language.java +++ b/src/main/java/org/fenix/llanfair/Language.java @@ -109,6 +109,7 @@ public enum Language { setting_footer_multiline, setting_footer_deltaLabels, setting_footer_sumOfBest, + setting_footer_worldRecord, // Accuracy accuracy_seconds, diff --git a/src/main/java/org/fenix/llanfair/Run.java b/src/main/java/org/fenix/llanfair/Run.java index 13be004..ef58eeb 100644 --- a/src/main/java/org/fenix/llanfair/Run.java +++ b/src/main/java/org/fenix/llanfair/Run.java @@ -1,6 +1,8 @@ package org.fenix.llanfair; import com.thoughtworks.xstream.annotations.XStreamOmitField; +import org.fenix.WorldRecord.Category; +import org.fenix.WorldRecord.WorldRecordParser; import org.fenix.llanfair.config.Settings; import org.fenix.utils.TableModelSupport; import org.fenix.utils.config.Configuration; @@ -104,7 +106,7 @@ public class Run implements TableModel, Serializable { public static final String COMPLETED_ATTEMPT_COUNTER_PROPERTY = "run.completedAttemptCounter"; - + public static final String RECORD_CATEGORY_PROPERTY = "run.record.category"; public static final String DELAYED_START_PROPERTY = "run.delayedStart"; @@ -185,6 +187,8 @@ public class Run implements TableModel, Serializable { @XStreamOmitField private int sessionAttempts; + private Category recordCategory; + // ----------------------------------------------------------- CONSTRUCTORS /** @@ -598,6 +602,23 @@ public class Run implements TableModel, Serializable { public int getSessionAttempts() { return sessionAttempts; } + public String getRecordString() { + String recordString; + + try { + recordString = WorldRecordParser.getRecord(this.recordCategory); + } catch (Exception e) { + recordString = "Unknown World Record"; + } + + return recordString; + } + + public Category getRecordCategory() + { + return recordCategory; + } + // ---------------------------------------------------------------- SETTERS public T getSetting( String key ) { @@ -648,6 +669,15 @@ public class Run implements TableModel, Serializable { pcSupport.firePropertyChange(DELAYED_START_PROPERTY, old, delayedStart); } + public void setRecordCategory(Category category) + { + Category old = this.recordCategory; + + this.recordCategory = category; + + pcSupport.firePropertyChange(RECORD_CATEGORY_PROPERTY, old, name); + } + /** * Inserts the given segment at the end. If it's the first segment being * added, the run becomes {@link State#READY}, meaning it can be started. @@ -1155,6 +1185,9 @@ public class Run implements TableModel, Serializable { if ( configuration == null ) { configuration = new Configuration(); } + if(recordCategory == null) { + recordCategory = new Category("", ""); + } } /** diff --git a/src/main/java/org/fenix/llanfair/config/Settings.java b/src/main/java/org/fenix/llanfair/config/Settings.java index fd4a3f8..c56217b 100644 --- a/src/main/java/org/fenix/llanfair/config/Settings.java +++ b/src/main/java/org/fenix/llanfair/config/Settings.java @@ -124,6 +124,7 @@ public class Settings { public static final Property footerMultiline = new Property<>( "footer.multiline" ); public static final Property footerShowDeltaLabels = new Property<>( "footer.deltaLabels" ); public static final Property footerShowSumOfBest = new Property<>( "footer.sumOfBest" ); + public static final Property footerShowWorldRecord = new Property<>("footer.worldRecord"); private static Configuration global = null; private static Run run = null; @@ -294,6 +295,7 @@ public class Settings { setDefault( footerMultiline.key, true, force ); setDefault( footerShowDeltaLabels.key, true, force ); setDefault( footerShowSumOfBest.key, false, force ); + setDefault( footerShowWorldRecord.key, true, force); } /** diff --git a/src/main/java/org/fenix/llanfair/dialog/EditRun.java b/src/main/java/org/fenix/llanfair/dialog/EditRun.java index 23dc9ec..86ce32d 100644 --- a/src/main/java/org/fenix/llanfair/dialog/EditRun.java +++ b/src/main/java/org/fenix/llanfair/dialog/EditRun.java @@ -4,6 +4,8 @@ import org.fenix.llanfair.*; import org.fenix.llanfair.config.Accuracy; import org.fenix.utils.gui.GBC; +import org.fenix.WorldRecord.*; + import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -107,6 +109,16 @@ implements ActionListener, ListSelectionListener { private JTextField runDelayedStart; + /** + * World record data from spedrun.com + */ + private JLabel recordLabel; + private JButton selectRecord; + private RecordDialog recordSelector; + private Category recordCategory; + private JLabel recordString; + + // ----------------------------------------------------------- CONSTRUCTEURS /** @@ -159,6 +171,20 @@ implements ActionListener, ListSelectionListener { moveUp = new JButton(Llanfair.getResources().getIcon("ARROW_UP.png")); moveDown = new JButton(Llanfair.getResources().getIcon("ARROW_DOWN.png")); segmented = new JCheckBox("" + Language.ED_SEGMENTED, run.isSegmented()); + recordLabel = new JLabel("World record"); + selectRecord = new JButton("Select record"); + recordCategory = run.getRecordCategory(); + + if(!recordCategory.getId().equals("")) { + try { + recordString = new JLabel(WorldRecordParser.getRecord(recordCategory)); + } catch(Exception e) { + RecordDialog.showError(); + } + } + else { + recordString = new JLabel(); + } placeComponents(); setBehavior(); @@ -186,10 +212,14 @@ implements ActionListener, ListSelectionListener { add(moveUp, GBC.grid(3, 5).insets(0, 4).anchor(GBC.FIRST_LINE_START)); add(moveDown, GBC.grid(3, 6).insets(4, 4).anchor(GBC.FIRST_LINE_START)); + add(recordLabel, GBC.grid(0,7).insets(5,4,4,0).anchor(GBC.LINE_END)); + add(selectRecord, GBC.grid(1,7).insets(4,0,0,4).anchor(GBC.LINE_START)); + add(recordString, GBC.grid(1,8).insets(4,0,0,4).anchor(GBC.LINE_START)); + JPanel controls = new JPanel(); controls.add(save); controls.add(cancel); - add(controls, GBC.grid(0, 7, 4, 1)); + add(controls, GBC.grid(0, 8, 4, 1)); } /** @@ -227,6 +257,7 @@ implements ActionListener, ListSelectionListener { moveDown.addActionListener(this); cancel.addActionListener(this); save.addActionListener(this); + selectRecord.addActionListener(this); // Insertion des délégués de rendus et d’édition. segments.setDefaultRenderer(Icon.class, new IconRenderer()); @@ -301,6 +332,8 @@ implements ActionListener, ListSelectionListener { long delayedStart = parseDelayedStartTime(runDelayedStart.getText()); run.setDelayedStart(delayedStart == -1 ? 0 : delayedStart); + run.setRecordCategory(recordCategory); + dispose(); } else if (source.equals(cancel)) { @@ -317,6 +350,9 @@ implements ActionListener, ListSelectionListener { int selected = segments.getSelectedRow(); run.moveSegmentDown(selected); segments.setRowSelectionInterval(selected + 1, selected + 1); + } else if (source.equals(selectRecord)) { + recordSelector = new RecordDialog(this); + recordSelector.display(); } } @@ -339,6 +375,13 @@ implements ActionListener, ListSelectionListener { moveDown.setEnabled(enabled && selected < run.getRowCount() - 1); } + public void recordSet() { + this.recordCategory = recordSelector.getCategory(); + this.recordString.setText(recordSelector.getRecordString()); + + this.recordSelector.dispose(); + } + // ---------------------------------------------------------- CLASSE INTERNE /** diff --git a/src/main/java/org/fenix/llanfair/dialog/TabComponents.java b/src/main/java/org/fenix/llanfair/dialog/TabComponents.java index 4f4f205..d973b1d 100644 --- a/src/main/java/org/fenix/llanfair/dialog/TabComponents.java +++ b/src/main/java/org/fenix/llanfair/dialog/TabComponents.java @@ -30,6 +30,7 @@ public class TabComponents extends SettingsTab SCB_SETTINGS.add(Settings.footerShowBestTime); SCB_SETTINGS.add(Settings.footerMultiline); SCB_SETTINGS.add(Settings.footerVerbose); + SCB_SETTINGS.add(Settings.footerShowWorldRecord); SCB_SETTINGS.add(Settings.footerShowSumOfBest); SCB_SETTINGS.add(Settings.coreShowSegmentName); SCB_SETTINGS.add(Settings.coreShowSplitTime); @@ -432,6 +433,7 @@ public class TabComponents extends SettingsTab footerPanel.add(checkBoxes.get(Settings.footerShowBestTime.getKey()), GBC.grid(1, 1).anchor(GBC.LINE_START)); footerPanel.add(checkBoxes.get(Settings.footerMultiline.getKey()), GBC.grid(1, 2).anchor(GBC.LINE_START)); footerPanel.add(checkBoxes.get(Settings.footerShowSumOfBest.getKey()), GBC.grid(0, 3).anchor(GBC.LINE_START)); + footerPanel.add(checkBoxes.get(Settings.footerShowWorldRecord.getKey()), GBC.grid(1,3).anchor(GBC.LINE_START)); footerPanel.setBorder( BorderFactory.createTitledBorder("" + Language.FOOTER) ); diff --git a/src/main/java/org/fenix/llanfair/gui/Footer.java b/src/main/java/org/fenix/llanfair/gui/Footer.java index 436c5e9..74fce40 100644 --- a/src/main/java/org/fenix/llanfair/gui/Footer.java +++ b/src/main/java/org/fenix/llanfair/gui/Footer.java @@ -29,6 +29,7 @@ class Footer extends JPanel { private static final int TEXT = 0x04; private static final int BEST = 0x08; private static final int VERBOSE = 0x10; + private static final int WORLD_RECORD = 0x20; private static final int INSET = 3; @@ -62,6 +63,8 @@ class Footer extends JPanel { private boolean resize; private Dimension preferredSize; + private JLabel worldRecord; + /** * Creates a default panel displaying informations for the given run. * @@ -88,6 +91,8 @@ class Footer extends JPanel { preferredSize = null; resize = false; + worldRecord = new JLabel(); + setRun(run); setOpaque(false); @@ -130,6 +135,7 @@ class Footer extends JPanel { boolean ftVerbose = Settings.footerVerbose.get(); boolean ftTwoLines = Settings.footerMultiline.get(); boolean ftSumOfBest = Settings.footerShowSumOfBest.get(); + boolean ftWorldRecord = Settings.footerShowWorldRecord.get(); int height = Math.max(timeH, labelH); int width = prevW + timeW + smtmW + INSET * 2; @@ -155,6 +161,10 @@ class Footer extends JPanel { if (ftSumOfBest) { height += labelH; } + if (ftWorldRecord) { + height += labelH; + } + preferredSize = new Dimension(width, height); setMinimumSize(new Dimension(50, height)); resize = false; @@ -220,7 +230,8 @@ class Footer extends JPanel { updateVisibility(BEST); forceResize(); - } else if (Settings.footerShowDeltaLabels.equals(property)) { + } else if (Settings.footerShowDeltaLabels.equals(property) + || Settings.footerShowWorldRecord.equals(property)) { updateVisibility(TEXT); forceResize(); } else if (Settings.footerVerbose.equals(property)) { @@ -242,6 +253,8 @@ class Footer extends JPanel { updateValues(TIME | TEXT); updateSize(); forceResize(); + } else if (Run.RECORD_CATEGORY_PROPERTY.equals(property)) { + updateValues(WORLD_RECORD); } } @@ -328,11 +341,20 @@ class Footer extends JPanel { ); panelSumOfBest.setOpaque(false); } + JPanel panelWorldRecord= new JPanel(new GridBagLayout()); + { + panelWorldRecord.add( + worldRecord, + GBC.grid(0, 0).insets(0, 0, 0, INSET) + ); + panelWorldRecord.setOpaque(false); + } add(timePanel, GBC.grid(0, 0).anchor(GBC.LINE_START).weight(0.5, 0.0)); add(deltaPanel, GBC.grid(1, 0).anchor(GBC.LINE_END).weight(0.5, 0.0)); add(panelBest, GBC.grid(0, 1).anchor(GBC.LINE_START).weight(0.5, 0.0)); add(panelDeltaBest, GBC.grid(1, 1).anchor(GBC.LINE_END).weight(0.5, 0.0)); add(panelSumOfBest, GBC.grid(0, 2).anchor(GBC.LINE_START).weight(0.5, 0.0)); + add(panelWorldRecord, GBC.grid(0,3, 2, 1).weight(0.5,0.0)); } private void updateVisibility(int identifier) { @@ -352,6 +374,7 @@ class Footer extends JPanel { labelDelta.setVisible(ftLabels && !ftVerbose); labelDeltaBest.setVisible(ftLabels); labelSumOfBest.setVisible(Settings.footerShowSumOfBest.get()); + worldRecord.setVisible(Settings.footerShowWorldRecord.get()); } if ((identifier & VERBOSE) == VERBOSE) { boolean ftVerbose = Settings.footerVerbose.get(); @@ -416,6 +439,7 @@ class Footer extends JPanel { labelBest.setForeground(color); labelDeltaBest.setForeground(color); labelSumOfBest.setForeground(color); + worldRecord.setForeground(color); } } @@ -445,6 +469,7 @@ class Footer extends JPanel { labelBest.setFont(Settings.coreFont.get()); labelDeltaBest.setFont(Settings.coreFont.get()); labelSumOfBest.setFont(Settings.coreFont.get()); + worldRecord.setFont(Settings.coreFont.get()); } } @@ -563,6 +588,9 @@ class Footer extends JPanel { labelDeltaBest.setText("" + Language.LB_FT_DELTA_BEST); labelSumOfBest.setText("" + Language.LB_FT_SUM_OF_BEST); } + if((identifier & WORLD_RECORD) == WORLD_RECORD) { + worldRecord.setText(run.getRecordString()); + } } private void updateSize() { diff --git a/src/main/resources/language.properties b/src/main/resources/language.properties index 66d6ae2..84684fe 100644 --- a/src/main/resources/language.properties +++ b/src/main/resources/language.properties @@ -90,6 +90,7 @@ setting_footer_bestTime = Best Time setting_footer_multiline = Display on Two Lines setting_footer_deltaLabels = Delta Labels setting_footer_sumOfBest = Sum of Best +setting_footer_worldRecord = World Record # Accuracy accuracy_seconds = Seconds