Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
Gered | 712144a65d | ||
Gered | c4985886aa | ||
Gered | cbe56800f3 | ||
Gered | 5b5c866a69 | ||
Gered | bbf7f5caa9 | ||
Gered | e23156f65c | ||
Gered | 26c712aaf2 | ||
c4d68f6b9d | |||
0fa050d80e | |||
4c87a025d6 | |||
bcc1003d1d | |||
Gered | d7b3e24411 | ||
Gered | 309ff7924a |
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -5,9 +5,12 @@
|
|||
target/
|
||||
out/
|
||||
build/
|
||||
classes/
|
||||
.project
|
||||
.classpath
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
/llanfair.xml
|
||||
/*.jar
|
||||
/*.app
|
13
README.md
13
README.md
|
@ -1,5 +1,9 @@
|
|||
# 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.
|
||||
|
@ -16,16 +20,19 @@ Note that Xunkar has started working on Llanfair v2.0 which is a complete rewrit
|
|||
|
||||
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.
|
||||
|
@ -64,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
|
||||
|
||||
|
|
|
@ -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.4'
|
||||
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 {
|
||||
|
|
32
src/main/java/org/fenix/WorldRecord/Category.java
Normal file
32
src/main/java/org/fenix/WorldRecord/Category.java
Normal 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;
|
||||
}
|
||||
}
|
32
src/main/java/org/fenix/WorldRecord/Game.java
Normal file
32
src/main/java/org/fenix/WorldRecord/Game.java
Normal 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;
|
||||
}
|
||||
}
|
58
src/main/java/org/fenix/WorldRecord/JSONReader.java
Normal file
58
src/main/java/org/fenix/WorldRecord/JSONReader.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
289
src/main/java/org/fenix/WorldRecord/RecordDialog.java
Normal file
289
src/main/java/org/fenix/WorldRecord/RecordDialog.java
Normal 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();
|
||||
}
|
||||
}
|
132
src/main/java/org/fenix/WorldRecord/WorldRecordParser.java
Normal file
132
src/main/java/org/fenix/WorldRecord/WorldRecordParser.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -117,7 +117,7 @@ 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() ) {
|
||||
|
@ -143,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();
|
||||
|
|
|
@ -109,6 +109,7 @@ public enum Language {
|
|||
setting_footer_multiline,
|
||||
setting_footer_deltaLabels,
|
||||
setting_footer_sumOfBest,
|
||||
setting_footer_worldRecord,
|
||||
|
||||
// Accuracy
|
||||
accuracy_seconds,
|
||||
|
|
|
@ -256,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -528,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;
|
||||
}
|
||||
|
|
|
@ -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> 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("", "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -124,6 +124,7 @@ public class Settings {
|
|||
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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 d’une 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.4<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> Xunkar<br> Gered King (minor enhancements only)<br><br><b>Localization provided by</b><br> Kokarn (Swedish)<br> MickeyG (Dutch)<br> Vulpone (German)<br> Xunkar (French)<br><br><b>Special Thanks to</b><br> 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> Xunkar<br> Gered King (minor enhancements only)<br><br><b>Localization provided by</b><br> Kokarn (Swedish)<br> MickeyG (Dutch)<br> Vulpone (German)<br> Xunkar (French)<br><br><b>Special Thanks to</b><br> ChristosOwen & Ketran</html>
|
Reference in a new issue