Compare commits

...

30 commits

Author SHA1 Message Date
Gered 712144a65d
Update README.md 2018-09-30 06:51:32 -04:00
Gered c4985886aa update gradle plugin versions 2018-06-20 20:42:36 -04:00
Gered cbe56800f3 update README.md 2018-06-20 20:41:40 -04:00
Gered 5b5c866a69 fix some dialog weirdness
instantiate all JDialog instances with proper parent component set. this
seems to make the ModalityType.APPLICATION_MODAL behave a bit better on
newer JDK versions.
2018-06-20 20:38:56 -04:00
Gered bbf7f5caa9 refactor error message display, and update RecordDialog error messages 2018-06-20 20:18:31 -04:00
Gered e23156f65c open category selection dialog centered over edit run dialog
as opposed to it showing up at the top-left of the screen always ;)
2018-06-20 19:09:05 -04:00
Gered 26c712aaf2
Merge pull request #24 from 4ilo/master
Get and display world record from speedrun.com
2018-06-20 18:42:26 -04:00
olivier c4d68f6b9d Bugfixes 2018-02-18 14:39:30 +01:00
olivier 0fa050d80e Complete world record in footer and settings 2018-02-18 14:23:06 +01:00
olivier 4c87a025d6 Complete world record in footer and settings 2018-02-18 14:22:42 +01:00
olivier bcc1003d1d start adding world record info from speedrun.com to timer and settings. 2018-02-12 19:33:44 +01:00
Gered d7b3e24411 update .gitignore 2017-01-28 16:56:59 -05:00
Gered 309ff7924a update version for development of next release 2017-01-28 16:56:14 -05:00
Gered 99821d6fd1 version 1.5.4 2017-01-28 16:49:14 -05:00
Gered 24a82f8b9e fix main window not resizing when sum of bests option is toggled on/off
was only being resized correctly on load
2017-01-28 16:48:29 -05:00
Gered a825d31a8e update README.md 2017-01-28 16:37:06 -05:00
Gered 25a2349715 add setting for specifying a custom default splits path
probably not terribly useful for most people, but for those like me who
keep their splits in Dropbox, it is handy
2017-01-28 15:13:24 -05:00
Gered 1b1d8b2330 fix usage of hardcoded colour values 2017-01-28 14:16:28 -05:00
Gered 51edfed385 add sum of best display and setting to toggle on/off. resolves #16 2017-01-28 13:54:09 -05:00
Gered 5e774934a4 fix xstream not processing annotations during serialization of runs
was saving the sessionAttempts counter even though it had the
XStreamOmitField annotation. not technically a problem, but not correct
2017-01-28 12:52:41 -05:00
Gered 8679a556ca add session counter. resolves #17 2017-01-28 12:47:59 -05:00
Gered 55788f58b5 tweak text label a bit 2017-01-28 12:15:14 -05:00
Gered 3ff5f782de fix trimming recent files list of excess files based on max setting 2017-01-28 12:12:40 -05:00
Gered 3f2ce3b810 add max recent files setting to the Settings > General dialog 2017-01-28 11:53:04 -05:00
Gered 6229197fba fix incorrect doc comment 2017-01-28 11:39:37 -05:00
Gered edacc0a359 #18: add max recent files list setting (default is now 10) 2017-01-28 11:36:53 -05:00
Gered b26e0ecf5e set default values after loading config for any missing keys
this fixes probably my main gripe with the existing XML serialization
code (at least as far as loading settings is concerned).
2017-01-28 11:35:57 -05:00
Gered 7a59e40b38 reset current file when choosing "New" from menu. fixes #19 2017-01-28 10:46:59 -05:00
Gered 208f56eae9 ensure settings saved even if user quits with Cmd+Q on mac. fixes #20 2017-01-28 10:30:18 -05:00
Gered 70bb8d3af6 update version for development of next release 2016-03-21 13:34:52 -04:00
24 changed files with 1114 additions and 178 deletions

5
.gitignore vendored
View file

@ -5,9 +5,12 @@
target/
out/
build/
classes/
.project
.classpath
*.iml
*.ipr
*.iws
/llanfair.xml
/llanfair.xml
/*.jar
/*.app

View file

@ -1,37 +1,38 @@
# Llanfair
**This project is not maintained anymore. I have not speedrun anything in over a year, and while I once thought I might come back to it again, that is highly unlikely to be the case anymore.**
---
[From the homepage](http://jenmaarai.com/llanfair/en/):
> Llanfair is a free software that helps speedrunners keep track of their run. Released in August 2012, its capacity for customization and its portability allowed it to garner some recognition in the scene. Developed in Java, Llanfair can run on Windows, MacOS, or Unix.
The original author Xavier "Xunkar" Sencert was kind enough to release the sources
(see [here](https://twitter.com/Xunkar/status/671042537134624768) and [here](https://twitter.com/Xunkar/status/671099823563632641))
when I asked. I'm not completely certain if Xunkar ever intends to continue development of Llanfair himself as it
seems he uses LiveSplit now (?).
Regardless, here I will be extending the original application as best I can by adding some missing features here and
when I asked. Here I will be extending the original application as best I can by adding some missing features here and
there and fixing bugs as needed.
## !! Beta Status Warning !!
Right now I consider the code and releases on this repository to be in a beta state. It is very, very possible that I
will make changes in the near future which will break compatibility with old config files / splits causing them not to
load in Llanfair. If this is a problem for you, I suggest that you hold off on using these releases for now!
Note that Xunkar has started working on Llanfair v2.0 which is a complete rewrite. You can
[check it's progress here](https://github.com/xunkar/llanfair).
## Download
Check the [releases page](https://github.com/gered/Llanfair/releases) for downloadable JARs.
Check the [releases page](https://github.com/gered/Llanfair/releases) for downloadable JARs.
JARs can be run from the command line via something similar to:
Llanfair requires Java 7 or later (you are encouraged to use the most recent version of Java).
Downloaded JARs can be run from the command line via something similar to:
```
$ java -jar Llanfair.jar
$ java -jar /path/to/Llanfair.jar
```
## Major Changes / Fixes
The main changes from v1.4.3 (the last official release from Xunkar) are as follows:
* Optional world record display, via run data from speedrun.com. Contributed by [4ilo](https://github.com/4ilo).
* Enhancements to JNativeHook support for global key events. Llanfair will prompt with an error
if the hook could not be registered instead of failing silently. Additionally on some OS's you
may see your OS prompt you with some kind of accessibility permissions request.
@ -40,7 +41,8 @@ The main changes from v1.4.3 (the last official release from Xunkar) are as foll
Xunkar had started after release of v1.4.3.
* Support for a delayed/negative run start time. Useful if you want to start the run at a time more convenient for you
but before any of the segments should start (e.g. to skip initial loading, fadeouts, etc).
* Attempt counter (both the number of total attempts and number of completed runs).
* "Sum of best" time display option.
* Attempt counter showing: the number of total attempts, number of completed runs and a per-session attempt counter.
* Additional font and colour customization settings.
* Coloring of split time deltas using slightly different color shades based on if you're gaining/losing time while
already ahead/behind.
@ -48,7 +50,11 @@ The main changes from v1.4.3 (the last official release from Xunkar) are as foll
* By default the config file is saved under `$user_home/.llanfair/` and the default location
to save/load splits is `$user_home/.llanfair/splits/` (though you can of course also choose
whatever other location you like).
* Ensure application settings are saved when a Mac user quits via Cmd+Q.
* Saved splits are now saved with a default `.lfs` file extension.
* Fix that prevents existing splits files from being accidentally overwritten when choosing "New" option from menu (after you already had a splits file open), and then choose "Save."
* User setting to control amount of files shown in the "Open Recent" menu list.
* Option to set a different default splits file directory (this is merely an additional convenience, most people probably won't use this).
* Other minor bug fixes.
### Important Note About Localization
@ -65,7 +71,7 @@ requests are more then welcome!).
You will need Gradle. Obviously any IDE with Gradle support will simply mean you can just open this project
right away in your IDE and get developing immediately. Easy.
Llanfair currently requires Java 7.
Llanfair currently requires Java 7 or later.
#### Command Line Building / Running / Distribution

View file

@ -5,14 +5,14 @@ buildscript {
plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.3'
id 'edu.sc.seis.macAppBundle' version '2.1.5'
id 'com.github.johnrengelman.shadow' version '2.0.4'
id 'edu.sc.seis.macAppBundle' version '2.2.1'
}
sourceCompatibility = 1.7
targetCompatibility = 1.7
version = '1.5.3'
version = '1.6-SNAPHSOT'
mainClassName = 'org.fenix.llanfair.Llanfair'
repositories {
@ -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 {

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,289 @@
package org.fenix.WorldRecord;
import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.dialog.EditRun;
import org.fenix.llanfair.dialog.LlanfairDialog;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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 LlanfairDialog
{
final private Llanfair master;
private JLabel searchLabel = new JLabel("Search game:");
private JButton searchButton = new JButton("Search");
private JTextField searchField = new JTextField();
private DefaultComboBoxModel<Game> gameListModel = new DefaultComboBoxModel<>();
private JComboBox<Game> games = new JComboBox<>(gameListModel);
private JLabel gamesLabel = new JLabel("Games:");
private DefaultComboBoxModel<Category> categoryListModel = new DefaultComboBoxModel<>();
private JComboBox<Category> 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, Llanfair master)
{
super(editRun);
this.master = master;
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<Game> games = new ArrayList<>();
try {
games = WorldRecordParser.searchGames(name);
} catch (IOException e)
{
master.showError("Error searching for matching games from speedrun.com.", e);
}
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<Category> categories = new ArrayList<>();
try {
categories = WorldRecordParser.getCategories(game);
} catch (IOException e)
{
master.showError("Error fetching game categories from speedrun.com.", e);
}
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)
{
master.showError("Error fetching game category world record time/owner from speedrun.com.", e);
}
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<Game> 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<Category> 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();
}
/**
* 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();
}
}

View file

@ -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<Game> searchGames(String name) throws IOException
{
String url = "https://www.speedrun.com/api/v1/games?name=" + name;
JSONArray json_games;
ArrayList<Game> 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<Category> getCategories(Game game) throws IOException
{
String url = "https://www.speedrun.com/api/v1/games/" + game.getId() + "/categories";
ArrayList<Category> 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;
}
}

View file

@ -56,7 +56,7 @@ final class Actions {
master = owner;
file = null;
fileChooser = new JFileChooser(UserSettings.getSplitsPath());
fileChooser = new JFileChooser(UserSettings.getSplitsPath(this.file));
fileChooser.setFileFilter(new FileNameExtensionFilter("" + Language.RUN_FILE_FILTER, "lfs"));
lastUnsplit = 0L;
@ -117,11 +117,12 @@ final class Actions {
MenuItem source = ( MenuItem ) event.getSource();
if ( source == MenuItem.EDIT ) {
EditRun dialog = new EditRun( run );
EditRun dialog = new EditRun( run, master );
dialog.display( true, master );
} else if ( source == MenuItem.NEW ) {
if ( confirmOverwrite() ) {
master.setRun( new Run() );
this.file = null;
}
} else if ( source == MenuItem.OPEN ) {
open( null );
@ -142,7 +143,7 @@ final class Actions {
} else if ( source == MenuItem.UNLOCK ) {
master.setLockedHotkeys(false);
} else if ( source == MenuItem.SETTINGS ) {
EditSettings dialog = new EditSettings();
EditSettings dialog = new EditSettings(master);
dialog.display( true, master );
} else if ( source == MenuItem.ABOUT ) {
about();
@ -253,10 +254,7 @@ final class Actions {
private File selectFile(FILE_CHOOSER_TYPE dialogType) {
int action = -1;
if (this.file == null)
fileChooser.setCurrentDirectory(new File(UserSettings.getSplitsPath()));
else
fileChooser.setCurrentDirectory(this.file);
fileChooser.setCurrentDirectory(new File(UserSettings.getSplitsPath(this.file)));
if (dialogType == FILE_CHOOSER_TYPE.OPEN)
action = fileChooser.showOpenDialog(master);
@ -420,6 +418,7 @@ final class Actions {
try {
XStream xml = new XStream( new DomDriver() );
SerializationUtils.customize(xml);
xml.autodetectAnnotations(true);
out = new BufferedOutputStream( new FileOutputStream( file ) );
xml.toXML( master.getRun(), out );
} catch ( Exception ex ) {

View file

@ -19,6 +19,8 @@ public enum Language {
// Settings > Generic
setting_alwaysOnTop,
setting_useDefaultSplitsPath,
setting_customSplitsPath,
setting_language,
setting_viewerLanguage,
setting_recentFiles,
@ -30,6 +32,7 @@ public enum Language {
setting_warnOnReset,
setting_windowUserResizable,
setting_windowWidth,
setting_maxRecentFiles,
// Settings > Color
setting_color_background,
@ -105,6 +108,8 @@ public enum Language {
setting_footer_bestTime,
setting_footer_multiline,
setting_footer_deltaLabels,
setting_footer_sumOfBest,
setting_footer_worldRecord,
// Accuracy
accuracy_seconds,
@ -142,6 +147,8 @@ public enum Language {
error_write_file,
error_import_run,
error_window_width,
error_max_recent_files,
error_splits_path,
// Actions
action_accept,
@ -188,6 +195,7 @@ public enum Language {
LB_FT_LIVE,
LB_FT_SEGMENT,
LB_FT_SPLIT,
LB_FT_SUM_OF_BEST,
/*
* Messages.
@ -248,6 +256,7 @@ public enum Language {
RUN_FILE_FILTER,
SEGMENT,
SEGMENTS,
SELECT_SPLITS_DIR,
SPLIT,
TIME,
UNTITLED,

View file

@ -46,6 +46,29 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
ToolTipManager.sharedInstance().setReshowDelay( 0 );
}
// this class only exists so that the "on app quit" logic is somewhere that can be
// added to the JVM's shutdown hook for Mac OS for when the user uses Cmd+Q to quit
public class AppShutdown implements Runnable {
private boolean hasRun = false;
public void run()
{
if (hasRun)
return;
Settings.save();
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException e) {
}
hasRun = true;
}
}
private Runnable onAppShutdown = new AppShutdown();
private Run run;
private RunPane runPane;
@ -79,6 +102,14 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
addComponentListener(this);
// if a mac user uses Cmd+Q to exit the application that quits the program in a way that doesn't fire
// the window closed event for some fucked up reason. wow
// the only "fancy" ways to deal with this problem that i could find are only relevant on Apple's Java 1.6
// runtime which everyone should avoid nowadays. so we'll do this instead...
if (System.getProperty("os.name").startsWith("Mac OS")) {
Runtime.getRuntime().addShutdownHook(new Thread(onAppShutdown));
}
run = new Run();
runPane = null;
lockedHotkeys = false;
@ -225,10 +256,22 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
*
* @param message the localized error message
*/
void showError( String message ) {
JOptionPane.showMessageDialog(
this, message, Language.ERROR.get(), JOptionPane.ERROR_MESSAGE
);
public void showError( String message ) {
showError(message, null);
}
public void showError(String message, Throwable ex) {
String errorMessage;
if (ex != null)
errorMessage = message + "\n\n" + ex.toString();
else
errorMessage = message;
JOptionPane pane = new JOptionPane(errorMessage, JOptionPane.ERROR_MESSAGE);
JDialog dialog = pane.createDialog(Language.ERROR.get());
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
dialog.dispose();
}
/**
@ -369,6 +412,7 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
|| Settings.footerShowDeltaLabels.equals(property)
|| Settings.footerVerbose.equals(property)
|| Settings.footerMultiline.equals(property)
|| Settings.footerShowSumOfBest.equals(property)
|| Settings.windowUserResizable.equals(property)
|| Settings.windowWidth.equals(property)
|| Run.NAME_PROPERTY.equals(property)) {
@ -428,12 +472,7 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
* unregister the native hook of {@code JNativeHook}.
*/
@Override public void windowClosed( WindowEvent event ) {
Settings.save();
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException e) {
}
onAppShutdown.run();
}
@Override public void windowClosing(WindowEvent event) {}
@ -501,7 +540,7 @@ public class Llanfair extends BorderlessFrame implements TableModelListener,
// problem. e.g. on OS X, if an exception is thrown a dialog telling the user that the
// application has requested some accessibility-related access shows up.
JOptionPane.showMessageDialog(this, Language.GLOBAL_HOTKEYS_STARTUP_ERROR, Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
showError(Language.GLOBAL_HOTKEYS_STARTUP_ERROR.get());
this.dispose();
return false;
}

View file

@ -46,7 +46,6 @@ enum MenuItem implements ActionListener {
*/
private static EventListenerList listeners = new EventListenerList();
private static final int MAX_FILES = 5;
private static final int TRUNCATE = 30;
private boolean isEndOfGroup;
@ -121,6 +120,14 @@ enum MenuItem implements ActionListener {
listeners.add( ActionListener.class, listener );
}
static void trimRecentFilesList(List<String> recentFiles) {
int numToTrim = recentFiles.size() - Settings.maxRecentFiles.get();
if (numToTrim > 0) {
for (int i = 0; i < numToTrim; ++i)
recentFiles.remove(recentFiles.size() - 1);
}
}
/**
* Callback to invoke whenever a file is opened. This method will sort the
* recent files menu to put the recently opened file at the top.
@ -136,23 +143,21 @@ enum MenuItem implements ActionListener {
}
recentFiles.add( 0, path );
if ( recentFiles.size() > MAX_FILES ) {
recentFiles.remove( MAX_FILES );
}
trimRecentFilesList(recentFiles);
Settings.recentFiles.set( recentFiles );
populateRecentlyOpened();
}
/**
* Fills the {@code OPEN_RECENT} item with the list of recently opened
* files. If {@code MAX_FILES} is somehow lower than the recent files list
* length, the overflowing files are removed.
* files.
*/
static void populateRecentlyOpened() {
List<String> recentFiles = Settings.recentFiles.get();
for ( int i = MAX_FILES; i < recentFiles.size(); i++ ) {
recentFiles.remove( i - 1 );
}
trimRecentFilesList(recentFiles);
Settings.recentFiles.set(recentFiles);
OPEN_RECENT.menuItem.removeAll();
for ( String fileName : Settings.recentFiles.get() ) {
String text = fileName;

View file

@ -1,5 +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;
@ -103,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";
@ -181,6 +184,11 @@ public class Run implements TableModel, Serializable {
private int numberOfCompletedAttempts;
@XStreamOmitField
private int sessionAttempts;
private Category recordCategory;
// ----------------------------------------------------------- CONSTRUCTORS
/**
@ -499,6 +507,23 @@ public class Run implements TableModel, Serializable {
// ------------------------------------------------------ INHERITED GETTERS
public Time getSumOfBest() {
long sum = 0;
for (int i = 0; i < segments.size(); ++i) {
Segment segment = segments.get(i);
Time best = segment.getTime(Segment.BEST);
Time live = segment.getTime(Segment.LIVE);
if (best != null || live != null) {
long bestMs = (best == null ? Long.MAX_VALUE : best.getMilliseconds());
long liveMs = (live == null ? Long.MAX_VALUE : live.getMilliseconds());
sum += Math.min(bestMs, liveMs);
}
}
return new Time(sum);
}
/**
* As specified by {@code TableModel}.
*/
@ -575,6 +600,25 @@ public class Run implements TableModel, Serializable {
return numberOfCompletedAttempts;
}
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> T getSetting( String key ) {
@ -625,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.
@ -709,6 +762,7 @@ public class Run implements TableModel, Serializable {
segments.get(current).setStartTime(startTime);
numberOfAttempts += 1;
sessionAttempts += 1;
pcSupport.firePropertyChange(ATTEMPT_COUNTER_PROPERTY, numberOfAttempts - 1, numberOfAttempts);
pcSupport.firePropertyChange(STATE_PROPERTY, State.READY, state);
@ -1113,13 +1167,14 @@ public class Run implements TableModel, Serializable {
* Initialize all transient fields.
*/
private void initializeTransients() {
pcSupport = new PropertyChangeSupport(this);
tmSupport = new TableModelSupport(this);
segmentsBackup = null;
stateBackup = null;
state = getRowCount() > 0 ? State.READY : State.NULL;
current = -1;
startTime = 0L;
pcSupport = new PropertyChangeSupport(this);
tmSupport = new TableModelSupport(this);
segmentsBackup = null;
stateBackup = null;
state = getRowCount() > 0 ? State.READY : State.NULL;
current = -1;
startTime = 0L;
sessionAttempts = 0;
if (subTitle == null) {
subTitle = "";
@ -1130,6 +1185,9 @@ public class Run implements TableModel, Serializable {
if ( configuration == null ) {
configuration = new Configuration();
}
if(recordCategory == null) {
recordCategory = new Category("", "");
}
}
/**

View file

@ -32,6 +32,7 @@ public class Settings {
public static final Property<Locale> language = new Property<>( "language" );
public static final Property<Locale> viewerLanguage = new Property<>( "viewerLanguage" );
public static final Property<List<String>> recentFiles = new Property<>( "recentFiles" );
public static final Property<Integer> maxRecentFiles = new Property<>( "maxRecentFiles" );
public static final Property<Point> coordinates = new Property<>( "coordinates" );
public static final Property<Dimension> dimension = new Property<>( "dimension" );
public static final Property<Compare> compareMethod = new Property<>( "compareMethod" );
@ -39,6 +40,8 @@ public class Settings {
public static final Property<Boolean> warnOnReset = new Property<>( "warnOnReset" );
public static final Property<Boolean> windowUserResizable = new Property<>( "windowUserResizable" );
public static final Property<Integer> windowWidth = new Property<>( "windowWidth" );
public static final Property<Boolean> useDefaultSplitsPath = new Property<>( "useDefaultSplitsPath" );
public static final Property<String> customSplitsPath = new Property<>( "customSplitsPath" );
/* COLOR properties */
@ -120,6 +123,8 @@ public class Settings {
public static final Property<Boolean> footerShowBestTime = new Property<>( "footer.bestTime" );
public static final Property<Boolean> footerMultiline = new Property<>( "footer.multiline" );
public static final Property<Boolean> footerShowDeltaLabels = new Property<>( "footer.deltaLabels" );
public static final Property<Boolean> footerShowSumOfBest = new Property<>( "footer.sumOfBest" );
public static final Property<Boolean> footerShowWorldRecord = new Property<>("footer.worldRecord");
private static Configuration global = null;
private static Run run = null;
@ -185,103 +190,112 @@ public class Settings {
/**
* Retrieves the configuration of Llanfair. The configuration is read from
* {@code llanfair.xml} placed in the working directory. If such a file
* cannot be found, a default configuration is loaded. No local
* configuration is loaded here, a call to {@code setRun} is required to
* {@code llanfair.xml} placed in the working directory. Default configuration
* values are applied for any values not present in the loaded configuration file.
* No local configuration is loaded here, a call to {@code setRun} is required to
* do just that. This method is lenient and called by the first property
* whose value is requested.
*/
private static void retrieve() {
global = Configuration.newInstance( new File(UserSettings.getSettingsPath() + File.separator + "llanfair.xml" ) );
if ( global.isEmpty() ) {
setDefaultValues();
}
setDefaultValues();
}
private static void setDefault(String key, Object value, boolean force) {
if (force || !global.contains(key))
global.put(key, value);
}
/**
* Fills the global configuration with every property, assigning them their
* default value. This method can be called even when the global
* configuration is not empty, and will thus function as a reset.
* Sets default values in the global configuration for each property which is
* missing (key is not present). Existing values are preserved (even nulls).
*/
private static void setDefaultValues() {
global.put( alwaysOnTop.key, true );
global.put( language.key, Locale.ENGLISH );
global.put( viewerLanguage.key, Locale.ENGLISH );
global.put( recentFiles.key, new ArrayList<String>() );
global.put( coordinates.key, null );
global.put( dimension.key, null );
global.put( compareMethod.key, Compare.BEST_OVERALL_RUN );
global.put( accuracy.key, Accuracy.TENTH );
global.put( warnOnReset.key, true );
global.put( windowUserResizable.key, true );
global.put( windowWidth.key, null );
boolean force = false;
global.put( colorBackground.key, Color.decode("0x000000") );
global.put( colorForeground.key, Color.decode( "0xc0c0c0" ) );
global.put( colorTime.key, Color.decode( "0xffffff" ) );
global.put( colorTimer.key, Color.decode( "0x22cc22" ) );
global.put( colorNegativeTime.key, Color.decode ("0x808080" ) );
global.put( colorTimeGainedWhileAhead.key, Color.decode( "0x6295fc" ) );
global.put( colorTimeLostWhileAhead.key, Color.decode( "0x99ccff" ) );
global.put( colorTimeGainedWhileBehind.key, Color.decode( "0xff8e8e" ) );
global.put( colorTimeLostWhileBehind.key, Color.decode( "0xe82323" ) );
global.put( colorNewRecord.key, Color.decode( "0xf0b012" ) );
global.put( colorTitle.key, Color.decode( "0xf0b012" ) );
global.put( colorSubTitle.key, Color.decode( "0xffffff" ) );
global.put( colorHighlight.key, Color.decode( "0xffffff" ) );
global.put( colorSeparators.key, Color.decode( "0x666666" ) );
setDefault( alwaysOnTop.key, true, force );
setDefault( language.key, Locale.ENGLISH, force );
setDefault( viewerLanguage.key, Locale.ENGLISH, force );
setDefault( recentFiles.key, new ArrayList<String>(), force );
setDefault( maxRecentFiles.key, 10, force );
setDefault( coordinates.key, null, force );
setDefault( dimension.key, null, force );
setDefault( compareMethod.key, Compare.BEST_OVERALL_RUN, force );
setDefault( accuracy.key, Accuracy.TENTH, force );
setDefault( warnOnReset.key, true, force );
setDefault( windowUserResizable.key, true, force );
setDefault( windowWidth.key, null, force );
setDefault( useDefaultSplitsPath.key, true, force );
setDefault( customSplitsPath.key, null, force );
global.put( useGlobalHotkeys.key, false );
global.put( hotkeySplit.key, -1 );
global.put( hotkeyUnsplit.key, -1 );
global.put( hotkeySkip.key, -1 );
global.put( hotkeyReset.key, -1 );
global.put( hotkeyStop.key, -1 );
global.put( hotkeyPause.key, -1 );
global.put( hotkeyLock.key, -1 );
setDefault( colorBackground.key, Color.decode("0x000000"), force );
setDefault( colorForeground.key, Color.decode( "0xc0c0c0" ), force );
setDefault( colorTime.key, Color.decode( "0xffffff" ), force );
setDefault( colorTimer.key, Color.decode( "0x22cc22" ), force );
setDefault( colorNegativeTime.key, Color.decode ("0x808080" ), force );
setDefault( colorTimeGainedWhileAhead.key, Color.decode( "0x6295fc" ), force );
setDefault( colorTimeLostWhileAhead.key, Color.decode( "0x99ccff" ), force );
setDefault( colorTimeGainedWhileBehind.key, Color.decode( "0xff8e8e" ), force );
setDefault( colorTimeLostWhileBehind.key, Color.decode( "0xe82323" ), force );
setDefault( colorNewRecord.key, Color.decode( "0xf0b012" ), force );
setDefault( colorTitle.key, Color.decode( "0xf0b012" ), force );
setDefault( colorSubTitle.key, Color.decode( "0xffffff" ), force );
setDefault( colorHighlight.key, Color.decode( "0xffffff" ), force );
setDefault( colorSeparators.key, Color.decode( "0x666666" ), force );
global.put( headerShowSubtitle.key, true );
global.put( headerShowTitle.key, true );
global.put( headerShowAttempts.key, true );
global.put( headerTitleFont.key, Font.decode( "Arial-14" ) );
global.put( headerSubTitleFont.key, Font.decode( "Arial-12" ) );
setDefault( useGlobalHotkeys.key, false, force );
setDefault( hotkeySplit.key, -1, force );
setDefault( hotkeyUnsplit.key, -1, force );
setDefault( hotkeySkip.key, -1, force );
setDefault( hotkeyReset.key, -1, force );
setDefault( hotkeyStop.key, -1, force );
setDefault( hotkeyPause.key, -1, force );
setDefault( hotkeyLock.key, -1, force );
global.put( historyRowCount.key, 8 );
global.put( historyTabular.key, true );
global.put( historyBlankRows.key, false );
global.put( historyMultiline.key, false );
global.put( historyMerge.key, Merge.LIVE );
global.put( historyLiveTimes.key, true );
global.put( historyDeltas.key, true );
global.put( historyIcons.key, true );
global.put( historyIconSize.key, 16 );
global.put( historyOffset.key, 0 );
global.put( historyAlwaysShowLast.key, true );
global.put( historySegmentFont.key, Font.decode( "Arial-12" ) );
global.put( historyTimeFont.key, Font.decode( "Arial-11" ) );
setDefault( headerShowSubtitle.key, true, force );
setDefault( headerShowTitle.key, true, force );
setDefault( headerShowAttempts.key, true, force );
setDefault( headerTitleFont.key, Font.decode( "Arial-14" ), force );
setDefault( headerSubTitleFont.key, Font.decode( "Arial-12" ), force );
global.put( coreAccuracy.key, Accuracy.HUNDREDTH );
global.put( coreShowIcons.key, true );
global.put( coreIconSize.key, 40 );
global.put( coreShowSegmentName.key, true );
global.put( coreShowSplitTime.key, false );
global.put( coreShowSegmentTime.key, true );
global.put( coreShowBestTime.key, true );
global.put( coreShowSegmentTimer.key, true );
global.put( coreTimerFont.key, Font.decode( "Digitalism-32" ) );
global.put( coreSegmentTimerFont.key, Font.decode( "Digitalism-18" ) );
global.put( coreFont.key, Font.decode( "Arial-12" ) );
global.put( coreOtherTimeFont.key, Font.decode( "Arial-11" ) );
setDefault( historyRowCount.key, 8, force );
setDefault( historyTabular.key, true, force );
setDefault( historyBlankRows.key, false, force );
setDefault( historyMultiline.key, false, force );
setDefault( historyMerge.key, Merge.LIVE, force );
setDefault( historyLiveTimes.key, true, force );
setDefault( historyDeltas.key, true, force );
setDefault( historyIcons.key, true, force );
setDefault( historyIconSize.key, 16, force );
setDefault( historyOffset.key, 0, force );
setDefault( historyAlwaysShowLast.key, true, force );
setDefault( historySegmentFont.key, Font.decode( "Arial-12" ), force );
setDefault( historyTimeFont.key, Font.decode( "Arial-11" ), force );
global.put( graphDisplay.key, true );
global.put( graphScale.key, 3.0F );
setDefault( coreAccuracy.key, Accuracy.HUNDREDTH, force );
setDefault( coreShowIcons.key, true, force );
setDefault( coreIconSize.key, 40, force );
setDefault( coreShowSegmentName.key, true, force );
setDefault( coreShowSplitTime.key, false, force );
setDefault( coreShowSegmentTime.key, true, force );
setDefault( coreShowBestTime.key, true, force );
setDefault( coreShowSegmentTimer.key, true, force );
setDefault( coreTimerFont.key, Font.decode( "Digitalism-32" ), force );
setDefault( coreSegmentTimerFont.key, Font.decode( "Digitalism-18" ), force );
setDefault( coreFont.key, Font.decode( "Arial-12" ), force );
setDefault( coreOtherTimeFont.key, Font.decode( "Arial-11" ), force );
global.put( footerDisplay.key, true );
global.put( footerVerbose.key, true );
global.put( footerUseSplitData.key, false );
global.put( footerShowBestTime.key, true );
global.put( footerMultiline.key, true );
global.put( footerShowDeltaLabels.key, true );
setDefault( graphDisplay.key, true, force );
setDefault( graphScale.key, 3.0F, force );
setDefault( footerDisplay.key, true, force );
setDefault( footerVerbose.key, true, force );
setDefault( footerUseSplitData.key, false, force );
setDefault( footerShowBestTime.key, true, force );
setDefault( footerMultiline.key, true, force );
setDefault( footerShowDeltaLabels.key, true, force );
setDefault( footerShowSumOfBest.key, false, force );
setDefault( footerShowWorldRecord.key, true, force);
}
/**

View file

@ -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;
@ -41,6 +43,8 @@ implements ActionListener, ListSelectionListener {
// -------------------------------------------------------------- ATTRIBUTS
final private Llanfair master;
/**
* Course éditée par cette boîte de dialogue.
*/
@ -107,6 +111,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
/**
@ -114,11 +128,12 @@ implements ActionListener, ListSelectionListener {
*
* @param run - la course a éditer.
*/
public EditRun(Run run) {
super();
public EditRun(Run run, Llanfair master) {
super(master);
if (run == null) {
throw new NullPointerException("EditDialog.EditDialog(): null run");
}
this.master = master;
this.run = run;
run.saveBackup();
@ -159,6 +174,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) {
this.master.showError("Error displaying selected world record information.", e);
}
}
else {
recordString = new JLabel();
}
placeComponents();
setBehavior();
@ -186,10 +215,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 +260,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());
@ -266,7 +300,7 @@ implements ActionListener, ListSelectionListener {
Time time = new Time(text);
result = time.getMilliseconds();
} catch (Exception e) {
JOptionPane.showMessageDialog(this, e.getMessage(), Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
master.showError("Invalid delayed start time.", e);
result = -1;
}
@ -301,6 +335,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 +353,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, master);
recordSelector.display(true, master);
}
}
@ -339,6 +378,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
/**
@ -440,8 +486,7 @@ implements ActionListener, ListSelectionListener {
try {
return super.stopCellEditing();
} catch (Exception e) {
JOptionPane.showMessageDialog(editor, e.getMessage(),
Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
master.showError(e.getMessage());
return false;
}
}

View file

@ -1,6 +1,14 @@
package org.fenix.llanfair.dialog;
import java.awt.GridBagLayout;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.Llanfair;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleDelegate;
import org.fenix.utils.locale.LocaleEvent;
import org.fenix.utils.locale.LocaleListener;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
@ -8,15 +16,6 @@ import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import org.fenix.llanfair.Language;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.gui.GBC;
import org.fenix.utils.locale.LocaleDelegate;
import org.fenix.utils.locale.LocaleEvent;
import org.fenix.utils.locale.LocaleListener;
/**
* ConfigDialog
*
@ -28,6 +27,8 @@ public class EditSettings extends LlanfairDialog
// ATTRIBUTS
final private Llanfair master;
/**
* Bouton permettant de valider et de fermer la boîte de dialogue.
*/
@ -42,7 +43,10 @@ public class EditSettings extends LlanfairDialog
/**
* Construction dune boîte de dialogue dédition de paramètres.
*/
public EditSettings() {
public EditSettings(Llanfair master) {
super(master);
this.master = master;
settingsTabs = new ArrayList<SettingsTab>();
settingsTabs.add(new TabGeneral());
settingsTabs.add(new TabLook());
@ -127,7 +131,7 @@ public class EditSettings extends LlanfairDialog
} catch (InvalidSettingException ex) {
ex.tab.requestFocusInWindow();
ex.field.requestFocusInWindow();
JOptionPane.showMessageDialog(this, ex.getMessage(), Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
master.showError(ex.getMessage());
}
} else if (source.equals(reset)) {
int option = JOptionPane.showConfirmDialog(this,
@ -155,7 +159,7 @@ public class EditSettings extends LlanfairDialog
dispose();
} catch (InvalidSettingException ex) {
ex.tab.grabFocus();
JOptionPane.showMessageDialog(this, ex.getMessage(), Language.ERROR.get(), JOptionPane.ERROR_MESSAGE);
master.showError(ex.getMessage());
}
}

View file

@ -3,6 +3,7 @@ package org.fenix.llanfair.dialog;
import org.fenix.llanfair.Llanfair;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
@ -16,6 +17,18 @@ public class LlanfairDialog extends JDialog {
// ATTRIBUTS
public LlanfairDialog() {
super();
}
public LlanfairDialog(Frame owner) {
super(owner);
}
public LlanfairDialog(JDialog owner) {
super(owner);
}
public void display(boolean lockNativeInputs, final Llanfair llanfair) {
if (lockNativeInputs) {
llanfair.setIgnoreNativeInputs(true);
@ -27,6 +40,7 @@ public class LlanfairDialog extends JDialog {
}
setAlwaysOnTop(true);
setModalityType(ModalityType.APPLICATION_MODAL);
setAutoRequestFocus(true);
pack();
setLocationRelativeTo(getOwner());
setVisible(true);

View file

@ -30,6 +30,8 @@ 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);
SCB_SETTINGS.add(Settings.coreShowSegmentTime);
@ -430,6 +432,8 @@ public class TabComponents extends SettingsTab
footerPanel.add(checkBoxes.get(Settings.footerShowDeltaLabels.getKey()), GBC.grid(1, 0).anchor(GBC.LINE_START));
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)
);

View file

@ -5,12 +5,14 @@ import org.fenix.llanfair.Llanfair;
import org.fenix.llanfair.config.Accuracy;
import org.fenix.llanfair.config.Compare;
import org.fenix.llanfair.config.Settings;
import org.fenix.utils.UserSettings;
import org.fenix.utils.gui.GBC;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Enumeration;
import java.util.Locale;
@ -30,6 +32,12 @@ public class TabGeneral extends SettingsTab implements ActionListener {
private JLabel alwaysOnTopText;
private JCheckBox useDefaultSplitsPath;
private JTextField customSplitsPath;
private JButton selectCustomSplitsPath;
private ButtonGroup compare;
private JLabel compareText;
@ -48,6 +56,10 @@ public class TabGeneral extends SettingsTab implements ActionListener {
private JLabel windowSizeUnitsText;
private JTextField maxRecentFiles;
private JLabel maxRecentFilesLabel;
// ----------------------------------------------------------- CONSTRUCTORS
TabGeneral() {
@ -59,6 +71,22 @@ public class TabGeneral extends SettingsTab implements ActionListener {
alwaysOnTop = new JCheckBox("" + Language.setting_alwaysOnTop);
alwaysOnTop.setSelected(Settings.alwaysOnTop.get());
useDefaultSplitsPath = new JCheckBox("" + Language.setting_useDefaultSplitsPath);
useDefaultSplitsPath.setSelected(Settings.useDefaultSplitsPath.get());
useDefaultSplitsPath.addActionListener(this);
String path = UserSettings.getSplitsPath(null);
if (path == null)
path = "";
customSplitsPath = new JTextField(path);
customSplitsPath.setEnabled(!Settings.useDefaultSplitsPath.get());
customSplitsPath.setColumns(30);
selectCustomSplitsPath = new JButton("" + Language.SELECT_SPLITS_DIR);
selectCustomSplitsPath.addActionListener(this);
selectCustomSplitsPath.setEnabled(!Settings.useDefaultSplitsPath.get());
compare = new ButtonGroup();
Compare setCmp = Settings.compareMethod.get();
for (Compare method : Compare.values()) {
@ -96,7 +124,11 @@ public class TabGeneral extends SettingsTab implements ActionListener {
windowSize.setEnabled(!windowUserResizable.isSelected());
windowSize.addActionListener(this);
maxRecentFiles = new JTextField("" + Settings.maxRecentFiles.get(), 4);
maxRecentFiles.addActionListener(this);
windowSizeUnitsText = new JLabel("" + Language.setting_windowWidth);
maxRecentFilesLabel = new JLabel("" + Language.setting_maxRecentFiles);
languageText = new JLabel("" + Language.setting_language);
alwaysOnTopText = new JLabel("" + Language.APPLICATION);
@ -125,6 +157,24 @@ public class TabGeneral extends SettingsTab implements ActionListener {
Settings.warnOnReset.set(warnOnReset.isSelected());
} else if (source.equals(windowUserResizable)) {
windowSize.setEnabled(!windowUserResizable.isSelected());
} else if (source.equals(useDefaultSplitsPath)) {
Settings.useDefaultSplitsPath.set(useDefaultSplitsPath.isSelected());
if (useDefaultSplitsPath.isEnabled())
Settings.customSplitsPath.set(null);
String path = UserSettings.getSplitsPath(null);
if (path == null)
path = "";
customSplitsPath.setText(path);
boolean enabled = !Settings.useDefaultSplitsPath.get();
customSplitsPath.setEnabled(enabled);
selectCustomSplitsPath.setEnabled(enabled);
} else if (source.equals(selectCustomSplitsPath)) {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int action = chooser.showOpenDialog(this);
if (action == JFileChooser.APPROVE_OPTION) {
customSplitsPath.setText(chooser.getSelectedFile().toString());
}
}
}
@ -146,6 +196,24 @@ public class TabGeneral extends SettingsTab implements ActionListener {
Settings.windowWidth.set(windowWidth);
}
int numRecentFiles;
try {
numRecentFiles = Integer.parseInt(maxRecentFiles.getText().trim());
}
catch (Exception ex) {
throw new InvalidSettingException(this, maxRecentFiles, "" + Language.error_max_recent_files);
}
Settings.maxRecentFiles.set(numRecentFiles);
if (!Settings.useDefaultSplitsPath.get()) {
String path = customSplitsPath.getText().trim();
if (!new File(path).exists()) {
throw new InvalidSettingException(this, customSplitsPath, "" + Language.error_splits_path);
}
Settings.customSplitsPath.set(path);
}
}
/**
@ -169,6 +237,22 @@ public class TabGeneral extends SettingsTab implements ActionListener {
add(alwaysOnTop, GBC.grid(1, 0).anchor(GBC.LINE_START));
add(warnOnReset, GBC.grid(1, 1).anchor(GBC.LINE_START));
JPanel panelSplitsPath = new JPanel(new GridBagLayout()); {
panelSplitsPath.add(
useDefaultSplitsPath,
GBC.grid(0, 0, 2, 1).anchor(GBC.LINE_START)
);
panelSplitsPath.add(
customSplitsPath,
GBC.grid(0, 1).anchor(GBC.LINE_START).insets(0, 5)
);
panelSplitsPath.add(
selectCustomSplitsPath,
GBC.grid(1, 1).anchor(GBC.LINE_START)
);
};
add(panelSplitsPath, GBC.grid(1, 2).anchor(GBC.LINE_START));
//add(languageText, GBC.grid(0, 2).anchor(GBC.LINE_END).insets(10, 10));
//add(language, GBC.grid(1, 2).fill(GBC.HORIZONTAL));
@ -196,6 +280,10 @@ public class TabGeneral extends SettingsTab implements ActionListener {
windowSizeContainer.add(windowSize);
windowSizeContainer.add(windowSizeUnitsText);
add(windowSizeContainer, GBC.grid(1, 6).anchor(GBC.LINE_START));
add(maxRecentFilesLabel, GBC.grid(0, 7).anchor(GBC.LINE_END).insets(5, 10));
add(maxRecentFiles, GBC.grid(1, 7).anchor(GBC.LINE_START).insets(0, 5));
}
// --------------------------------------------------------- INTERNAL TYPES

View file

@ -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;
@ -56,9 +57,14 @@ class Footer extends JPanel {
private JPanel panelBest; // labelBest + best
private JPanel panelDeltaBest; // labelDeltaBest + deltaBest
private JLabel labelSumOfBest;
private JLabel sumOfBest;
private boolean resize;
private Dimension preferredSize;
private JLabel worldRecord;
/**
* Creates a default panel displaying informations for the given run.
*
@ -73,16 +79,20 @@ class Footer extends JPanel {
deltaBest = new JLabel();
inlineBest = new JLabel();
inlineDeltaBest = new JLabel();
sumOfBest = new JLabel();
labelLive = new JLabel();
labelPrev = new JLabel();
labelBest = new JLabel();
labelDelta = new JLabel();
labelDeltaBest = new JLabel();
labelSumOfBest = new JLabel();
preferredSize = null;
resize = false;
worldRecord = new JLabel();
setRun(run);
setOpaque(false);
@ -124,6 +134,8 @@ class Footer extends JPanel {
boolean ftLabels = Settings.footerShowDeltaLabels.get();
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;
@ -146,6 +158,13 @@ class Footer extends JPanel {
width += 5;
}
}
if (ftSumOfBest) {
height += labelH;
}
if (ftWorldRecord) {
height += labelH;
}
preferredSize = new Dimension(width, height);
setMinimumSize(new Dimension(50, height));
resize = false;
@ -211,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)) {
@ -224,9 +244,17 @@ class Footer extends JPanel {
} else if (Settings.coreOtherTimeFont.equals(property)) {
updateFonts(TIME | DELTA);
forceResize();
} else if (Settings.windowUserResizable.equals(property) || Settings.windowWidth.equals(property)) {
} else if (Settings.windowUserResizable.equals(property)
|| Settings.windowWidth.equals(property)) {
updateSize();
forceResize();
} else if (Settings.footerShowSumOfBest.equals(property)) {
updateVisibility(BEST | TEXT);
updateValues(TIME | TEXT);
updateSize();
forceResize();
} else if (Run.RECORD_CATEGORY_PROPERTY.equals(property)) {
updateValues(WORLD_RECORD);
}
}
@ -301,10 +329,32 @@ class Footer extends JPanel {
panelDeltaBest.add(deltaBest, GBC.grid(1, 0).anchor(GBC.LINE_END));
panelDeltaBest.setOpaque(false);
}
JPanel panelSumOfBest = new JPanel(new GridBagLayout());
{
panelSumOfBest.add(
labelSumOfBest,
GBC.grid(0, 0).anchor(GBC.LINE_START).insets(0, 0, 0, INSET)
);
panelSumOfBest.add(
sumOfBest,
GBC.grid(1, 0).anchor(GBC.LINE_END).insets(0, 0, 0, INSET)
);
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) {
@ -315,6 +365,7 @@ class Footer extends JPanel {
panelDeltaBest.setVisible(ftTwoLines);
inlineBest.setVisible(!ftTwoLines && ftBest);
inlineDeltaBest.setVisible(!ftTwoLines && ftBest);
sumOfBest.setVisible(Settings.footerShowSumOfBest.get());
}
if ((identifier & TEXT) == TEXT) {
boolean ftLabels = Settings.footerShowDeltaLabels.get();
@ -322,6 +373,8 @@ class Footer extends JPanel {
labelLive.setVisible(ftLabels && ftVerbose);
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();
@ -354,6 +407,7 @@ class Footer extends JPanel {
time.setForeground(colorTM);
best.setForeground(colorTM);
inlineBest.setForeground(colorTM);
sumOfBest.setForeground(colorTM);
}
if ((identifier & DELTA) == DELTA) {
if (run.hasPreviousSegment()) {
@ -384,6 +438,8 @@ class Footer extends JPanel {
labelLive.setForeground(color);
labelBest.setForeground(color);
labelDeltaBest.setForeground(color);
labelSumOfBest.setForeground(color);
worldRecord.setForeground(color);
}
}
@ -399,6 +455,7 @@ class Footer extends JPanel {
time.setFont(Settings.coreOtherTimeFont.get());
best.setFont(Settings.coreOtherTimeFont.get());
inlineBest.setFont(Settings.coreOtherTimeFont.get());
sumOfBest.setFont(Settings.coreOtherTimeFont.get());
}
if ((identifier & DELTA) == DELTA) {
delta.setFont(Settings.coreOtherTimeFont.get());
@ -411,6 +468,8 @@ class Footer extends JPanel {
labelLive.setFont(Settings.coreFont.get());
labelBest.setFont(Settings.coreFont.get());
labelDeltaBest.setFont(Settings.coreFont.get());
labelSumOfBest.setFont(Settings.coreFont.get());
worldRecord.setFont(Settings.coreFont.get());
}
}
@ -453,6 +512,11 @@ class Footer extends JPanel {
best.setText("");
inlineBest.setText("");
}
Time sumOfBestTime = run.getSumOfBest();
if (sumOfBestTime.getMilliseconds() > 0)
sumOfBest.setText(sumOfBestTime.toString());
else
sumOfBest.setText("");
}
if ((identifier & DELTA) == DELTA) {
if (hasPrevious) {
@ -522,6 +586,10 @@ class Footer extends JPanel {
labelBest.setText("" + Language.LB_FT_BEST);
labelDelta.setText("" + Language.LB_FT_DELTA);
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());
}
}

View file

@ -97,6 +97,11 @@ public class RunPane extends JPanel {
*/
private JLabel attemptCounter;
/**
* Label displaying the number of attempts the user has made of this run in this session only.
*/
private JLabel sessionAttemptCounter;
/**
* A list containing empty labels serving as separators.
*/
@ -137,14 +142,15 @@ public class RunPane extends JPanel {
if (run == null) {
throw new NullPointerException("null run");
}
title = new JLabel();
subTitle = new JLabel();
attemptCounter = new JLabel();
core = new Core(run);
graph = new Graph(run);
history = new History(run);
footer = new Footer(run);
separators = new ArrayList<JLabel>();
title = new JLabel();
subTitle = new JLabel();
attemptCounter = new JLabel();
sessionAttemptCounter = new JLabel();
core = new Core(run);
graph = new Graph(run);
history = new History(run);
footer = new Footer(run);
separators = new ArrayList<JLabel>();
title.setHorizontalAlignment(SwingConstants.CENTER);
subTitle.setHorizontalAlignment(SwingConstants.CENTER);
@ -288,13 +294,14 @@ public class RunPane extends JPanel {
* Places the sub-components within this component.
*/
private void placeComponents() {
add(title, GBC.grid(0, 0).insets(3, 0, 1, 0).fill(GBC.BOTH));
add(subTitle, GBC.grid(0, 1).insets(3, 0, 0, 0).fill(GBC.BOTH));
add(attemptCounter, GBC.grid(0, 2).insets(1, 0, 1, 3).anchor(GBC.LINE_END));
add(createSeparator(), GBC.grid(0, 3).insets(3, 0).fill(GBC.HORIZONTAL));
add(history, GBC.grid(0, 4).fill(GBC.HORIZONTAL).anchor(GBC.NORTH).insets(0, 5));
add(createSeparator(), GBC.grid(0, 5).insets(3, 0).fill(GBC.HORIZONTAL));
add(createSeparator(), GBC.grid(0, 7).insets(3, 0, 0, 0).fill(GBC.HORIZONTAL));
add(title, GBC.grid(0, 0, 2, 1).insets(3, 0, 1, 0).fill(GBC.BOTH));
add(subTitle, GBC.grid(0, 1, 2, 1).insets(3, 0, 0, 0).fill(GBC.BOTH));
add(sessionAttemptCounter, GBC.grid(0, 2, 1, 1).insets(1, 3, 1, 0).anchor(GBC.LINE_START));
add(attemptCounter, GBC.grid(1, 2, 1, 1).insets(1, 0, 1, 3).anchor(GBC.LINE_END));
add(createSeparator(), GBC.grid(0, 3, 2, 1).insets(3, 0).fill(GBC.HORIZONTAL));
add(history, GBC.grid(0, 4, 2, 1).fill(GBC.HORIZONTAL).anchor(GBC.NORTH).insets(0, 5));
add(createSeparator(), GBC.grid(0, 5, 2, 1).insets(3, 0).fill(GBC.HORIZONTAL));
add(createSeparator(), GBC.grid(0, 7, 2, 1).insets(3, 0, 0, 0).fill(GBC.HORIZONTAL));
updateVisibility(ALL);
}
@ -319,6 +326,9 @@ public class RunPane extends JPanel {
attemptCounter.setText(String.format("%d", attempts));
else
attemptCounter.setText(String.format("%d / %d", completedAttempts, attempts));
int sessionAttempts = run.getSessionAttempts();
sessionAttemptCounter.setText(String.format("%d", sessionAttempts));
}
if ((identifier & SEPARATOR) == SEPARATOR) {
boolean hdTitle = Settings.headerShowSubtitle.get();
@ -353,7 +363,8 @@ public class RunPane extends JPanel {
}
}
attemptCounter.setForeground(Color.WHITE);
attemptCounter.setForeground(Settings.colorForeground.get());
sessionAttemptCounter.setForeground(Settings.colorForeground.get());
}
/**
@ -369,6 +380,7 @@ public class RunPane extends JPanel {
}
if ((identifier & ATTEMPTS) == ATTEMPTS) {
attemptCounter.setFont(Settings.coreFont.get());
sessionAttemptCounter.setFont(Settings.coreFont.get());
}
}
@ -382,19 +394,19 @@ public class RunPane extends JPanel {
if ((identifier & GRAPH) == GRAPH) {
if (Settings.graphDisplay.get()) {
remove(core);
add(core, GBC.grid(0, 6).insets(0, 5).fill(GBC.HORIZONTAL));
add(graph, GBC.grid(0, 8).fill(GBC.BOTH).insets(0, 0, 3, 0)
add(core, GBC.grid(0, 6, 2, 1).insets(0, 5).fill(GBC.HORIZONTAL));
add(graph, GBC.grid(0, 8, 2, 1).fill(GBC.BOTH).insets(0, 0, 3, 0)
.weight(1.0, 1.0));
} else {
remove(graph);
remove(core);
add(core, GBC.grid(0, 6).insets(0, 5).fill(GBC.HORIZONTAL)
add(core, GBC.grid(0, 6, 2, 1).insets(0, 5).fill(GBC.HORIZONTAL)
.weight(1.0, 1.0));
}
}
if ((identifier & FOOTER) == FOOTER) {
if (Settings.footerDisplay.get()) {
add(footer, GBC.grid(0, 9).insets(0, 3).fill(GBC.HORIZONTAL));
add(footer, GBC.grid(0, 9, 2, 1).insets(0, 3).fill(GBC.HORIZONTAL));
} else {
remove(footer);
}
@ -402,9 +414,9 @@ public class RunPane extends JPanel {
if ((identifier & SUBTITLE) == SUBTITLE) {
if (Settings.headerShowSubtitle.get()) {
if (Settings.headerShowTitle.get()) {
add(subTitle, GBC.grid(0, 1));
add(subTitle, GBC.grid(0, 1, 2, 1));
} else {
add(subTitle, GBC.grid(0, 1).insets(3, 0, 0, 0));
add(subTitle, GBC.grid(0, 1, 2, 1).insets(3, 0, 0, 0));
}
} else {
remove(subTitle);
@ -415,6 +427,7 @@ public class RunPane extends JPanel {
}
if ((identifier & ATTEMPTS) == ATTEMPTS) {
attemptCounter.setVisible(Settings.headerShowAttempts.get());
sessionAttemptCounter.setVisible(Settings.headerShowAttempts.get());
}
revalidate();
repaint();

View file

@ -1,5 +1,7 @@
package org.fenix.utils;
import org.fenix.llanfair.config.Settings;
import java.io.File;
public class UserSettings {
@ -31,7 +33,19 @@ public class UserSettings {
* Returns the path to the location where splits can be saved in. This is
* located as a subdirectory within the user settings directory.
*/
public static String getSplitsPath() {
public static String getDefaultSplitsPath() {
return splitsPath.getPath();
}
public static String getSplitsPath(File selectedFile) {
if (selectedFile != null)
return selectedFile.toString();
else {
if (Settings.useDefaultSplitsPath.get())
return UserSettings.getDefaultSplitsPath();
else
return Settings.customSplitsPath.get();
}
}
}

View file

@ -1,5 +1,7 @@
# Settings > Generic
setting_alwaysOnTop = Always on Top
setting_useDefaultSplitsPath = Use Default Splits Directory
setting_customSplitsPath = Splits Directory
setting_language = Language
setting_viewerLanguage = Viewer's Language
setting_recentFiles =
@ -11,6 +13,7 @@ setting_locked =
setting_warnOnReset = Warn on Reset if better times
setting_windowUserResizable = User Resizable
setting_windowWidth = Fixed Width (Pixels)
setting_maxRecentFiles = Recent Files Limit
# Settings > Color
setting_color_background = Background
@ -86,6 +89,8 @@ setting_footer_verbose = Show More Info
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
@ -123,6 +128,8 @@ error_read_file = "{0}" isn't a valid Llanfair run or you do not have permission
error_write_file = You do not have permission to write in that folder.
error_import_run = "{0}" isn't a recognized run file.
error_window_width = Window Width must be a positive integer.
error_max_recent_files = Recent files limit must be a positive integer.
error_splits_path = Splits Directory cannot be read or does not exist.
# Actions
action_accept =
@ -169,6 +176,7 @@ LB_FT_DELTA_BEST = vs Best:
LB_FT_LIVE = Live:
LB_FT_SEGMENT = Prev. Segment:
LB_FT_SPLIT = Prev. Split:
LB_FT_SUM_OF_BEST = Sum of Best:
# Messages
ICON_TOO_BIG =
@ -222,6 +230,7 @@ RUN_TITLE = Run Title
RUN_FILE_FILTER = Llanfair Run Splits
SEGMENT = Time (Segment)
SEGMENTS = Segments
SELECT_SPLITS_DIR = Choose ...
SPLIT = Split:
TIME = Time (Split)
UNTITLED = <untitled>

View file

@ -2,4 +2,4 @@ website = https://github.com/gered/Llanfair
# donate = https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EPTSR32CQTHBY
about = <html><div width=280 align=justify>Llanfair 1.5.3<br><br>This application is distributed under the creative commons licence <b>BY-NC-SA</b> which stipulates that you must abide by the following rules: the application must be redistributed under the same licence, the name(s) of the author(s) must always be cited and you cannot make a commercial use of it.<br><br>NOTE: This version of Llanfair is a fork of the original by Xunkar. Any issues with this version should not be brought to him.<br><br><b>Author</b><br>&nbsp;&nbsp;Xunkar<br>&nbsp;&nbsp;Gered King (minor enhancements only)<br><br><b>Localization provided by</b><br>&nbsp;&nbsp;Kokarn (Swedish)<br>&nbsp;&nbsp;MickeyG (Dutch)<br>&nbsp;&nbsp;Vulpone (German)<br>&nbsp;&nbsp;Xunkar (French)<br><br><b>Special Thanks to</b><br>&nbsp;&nbsp;ChristosOwen & Ketran</html>
about = <html><div width=280 align=justify>Llanfair 1.6 (Development Snapshot)<br><br>This application is distributed under the creative commons licence <b>BY-NC-SA</b> which stipulates that you must abide by the following rules: the application must be redistributed under the same licence, the name(s) of the author(s) must always be cited and you cannot make a commercial use of it.<br><br>NOTE: This version of Llanfair is a fork of the original by Xunkar. Any issues with this version should not be brought to him.<br><br><b>Author</b><br>&nbsp;&nbsp;Xunkar<br>&nbsp;&nbsp;Gered King (minor enhancements only)<br><br><b>Localization provided by</b><br>&nbsp;&nbsp;Kokarn (Swedish)<br>&nbsp;&nbsp;MickeyG (Dutch)<br>&nbsp;&nbsp;Vulpone (German)<br>&nbsp;&nbsp;Xunkar (French)<br><br><b>Special Thanks to</b><br>&nbsp;&nbsp;ChristosOwen & Ketran</html>