pushed local repository online
109
src/leylines/dialog/Dialog.java
Normal file
@ -0,0 +1,109 @@
|
||||
package leylines.dialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class Dialog {
|
||||
|
||||
private static final String STANDART_DIALOG_FOLDER = "res/dialog/";
|
||||
|
||||
public static final int WAITING_ANSWER_STATE = -1;
|
||||
public static final int NORMAL_STATE = 0;
|
||||
|
||||
Replica currentReplica = null;
|
||||
Replica nextReplica;
|
||||
ReplicaActionHandler rah;
|
||||
|
||||
Random rand = new Random();
|
||||
|
||||
List<Replica> options = null;
|
||||
int ans = -1;
|
||||
|
||||
public Dialog(String name) {
|
||||
currentReplica = nextReplica = parseReplicaFromXML(name);
|
||||
rah = nextReplica.handler;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
if (hasNext()) {
|
||||
currentReplica = nextReplica;
|
||||
|
||||
if (currentReplica.answers.isEmpty()) {
|
||||
nextReplica = null;
|
||||
} else if (currentReplica.answers.size() == 1) {
|
||||
nextReplica = currentReplica.answers.get(0);
|
||||
} else {
|
||||
if (currentReplica.owner == Replica.OWNER_AI && nextReplicaIsMine()) {
|
||||
options = currentReplica.answers;
|
||||
if (ans == WAITING_ANSWER_STATE) {
|
||||
return;
|
||||
}
|
||||
nextReplica = currentReplica.answers.get(ans);
|
||||
ans = WAITING_ANSWER_STATE;
|
||||
options = null;
|
||||
} else {
|
||||
int ai_ans = rand.nextInt(currentReplica.answers.size() - 1);
|
||||
if (rah != null) {
|
||||
int id = rah.executeScriptLogic();
|
||||
for (int i = 0; i < currentReplica.answers.size(); i++) {
|
||||
if (currentReplica.answers.get(i).id == id) {
|
||||
ai_ans = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.err.println("dialog handler unaviable, choosing random option");
|
||||
}
|
||||
nextReplica = currentReplica.answers.get(ai_ans);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return nextReplica != null;
|
||||
}
|
||||
|
||||
public Replica getCurrentReplica() {
|
||||
return currentReplica;
|
||||
}
|
||||
|
||||
public List<Replica> getCurrentOptionsList() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setAnswer(Replica r) {
|
||||
ans = options.indexOf(r);
|
||||
}
|
||||
|
||||
public void setAnswer(int i) {
|
||||
ans = i;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
if (currentReplica.answers.size() > 1 && nextReplicaIsMine() && ans == -1) {
|
||||
return WAITING_ANSWER_STATE;
|
||||
} else {
|
||||
return NORMAL_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean nextReplicaIsMine() {
|
||||
return currentReplica.answers.get(0).owner == Replica.OWNER_ME;
|
||||
}
|
||||
|
||||
private Replica parseReplicaFromXML(String file) {
|
||||
try {
|
||||
Parser parser = new Parser();
|
||||
return parser.readFromFile(STANDART_DIALOG_FOLDER + file + ".xml");
|
||||
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
198
src/leylines/dialog/Parser.java
Normal file
@ -0,0 +1,198 @@
|
||||
package leylines.dialog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class Parser {
|
||||
|
||||
public static String defaultFolder = "dialogs/";
|
||||
|
||||
Map<Integer, Replica> allReplicas = new HashMap<Integer, Replica>();
|
||||
|
||||
Replica readFromFile(String file) throws ParserConfigurationException, SAXException, IOException {
|
||||
File xmlFile = new File(file);
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.parse(xmlFile);
|
||||
doc.getDocumentElement().normalize();
|
||||
Element dialog = (Element) doc.getElementsByTagName("dialog").item(0);
|
||||
ReplicaActionHandler dbgObj = null;
|
||||
if (dialog.hasAttribute("handler")) {
|
||||
String handlerString = dialog.getAttribute("handler");
|
||||
Class<ReplicaActionHandler> rah = null;
|
||||
try {
|
||||
rah = (Class<ReplicaActionHandler>) Class.forName("game.dialog." + handlerString);
|
||||
dbgObj = rah.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
NodeList replicaList = dialog.getElementsByTagName("replica");
|
||||
for (int i = 0; i < replicaList.getLength(); i++) {
|
||||
Element replicaXML = (Element) replicaList.item(i);
|
||||
Replica replica = new Replica(replicaXML.getTextContent());
|
||||
int id = Integer.parseInt(replicaXML.getAttribute("id"));
|
||||
replica.id = id;
|
||||
allReplicas.put(id, replica);
|
||||
if (replicaXML.hasAttribute("owner")) {
|
||||
replica.owner = Replica.OWNER_AI;
|
||||
}
|
||||
replica.handler = dbgObj;
|
||||
}
|
||||
|
||||
String answers;
|
||||
for (int i = 0; i < replicaList.getLength(); i++) {
|
||||
Element replicaXML = (Element) replicaList.item(i);
|
||||
int currentReplicaID = Integer.parseInt(replicaXML.getAttribute("id"));
|
||||
if (replicaXML.hasAttribute("answers")) {
|
||||
answers = replicaXML.getAttribute("answers");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
String[] ansIDs = answers.split(" ");
|
||||
for (int j = 0; j < ansIDs.length; j++) {
|
||||
int id = Integer.parseInt(ansIDs[j]);
|
||||
allReplicas.get(currentReplicaID).answers.add(allReplicas.get(id));
|
||||
}
|
||||
}
|
||||
|
||||
return allReplicas.get(0);
|
||||
}
|
||||
|
||||
// replica list is different from dialog - it can contain orphan nodes
|
||||
// and not necesserily should be traversed recurcively
|
||||
public Document translateReplicaListToXML(List<Replica> replicaList) throws ParserConfigurationException{
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.newDocument();
|
||||
|
||||
Element root = doc.createElement("dialog"); //root
|
||||
doc.appendChild(root);
|
||||
|
||||
for(Replica replica: replicaList){
|
||||
Element replicaElement = doc.createElement("replica");
|
||||
|
||||
replicaElement.setAttribute("id", ""+replica.id);
|
||||
replicaElement.setTextContent(replica.text);
|
||||
//replicaElement.setAttribute("text", replica.text);
|
||||
if(replica.owner == Replica.OWNER_AI) {
|
||||
replicaElement.setAttribute("owner", "AI");
|
||||
}
|
||||
String answers = "";
|
||||
for(Replica ans: replica.answers){
|
||||
answers += ""+ans.id;
|
||||
// add space if this answer is not the last one
|
||||
if(replica.answers.indexOf(ans) != replica.answers.size() - 1){
|
||||
answers += ' ';
|
||||
}
|
||||
}
|
||||
if(!answers.isBlank()) replicaElement.setAttribute("answers", answers);
|
||||
|
||||
root.appendChild(replicaElement);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
public void printXMlToStream(Document document, OutputStream stream) {
|
||||
stripEmptyElements(document);
|
||||
|
||||
try {
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
transformerFactory.setAttribute("indent-number", 4);
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
|
||||
DOMSource source = new DOMSource(document);
|
||||
StreamResult result = new StreamResult(stream);
|
||||
transformer.transform(source, result);
|
||||
} catch (TransformerException e) {
|
||||
System.err.println("Cannot print XML to steam. " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public void populateReplicaListFromFile(List<Replica> listToPopulate, File toReadFrom) throws IOException, SAXException {
|
||||
Map<Integer, Replica> allReplicas = new HashMap<Integer, Replica>();
|
||||
try {
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.parse(toReadFrom);
|
||||
doc.getDocumentElement().normalize();
|
||||
Element dialog = (Element) doc.getElementsByTagName("dialog").item(0);
|
||||
|
||||
// TODO: NO HANDLER PARSING JUST YET, WAIT FOR SCRIPT-BASED IMPLEMENTATION
|
||||
|
||||
NodeList replicaList = dialog.getElementsByTagName("replica");
|
||||
for (int i = 0; i < replicaList.getLength(); i++) {
|
||||
Element replicaXML = (Element) replicaList.item(i);
|
||||
Replica replica = new Replica(replicaXML.getTextContent());
|
||||
int id = Integer.parseInt(replicaXML.getAttribute("id"));
|
||||
replica.id = id;
|
||||
allReplicas.put(id, replica);
|
||||
if (replicaXML.hasAttribute("owner")) {
|
||||
replica.owner = Replica.OWNER_AI;
|
||||
}
|
||||
}
|
||||
|
||||
String answers;
|
||||
for (int i = 0; i < replicaList.getLength(); i++) {
|
||||
Element replicaXML = (Element) replicaList.item(i);
|
||||
int currentReplicaID = Integer.parseInt(replicaXML.getAttribute("id"));
|
||||
if (replicaXML.hasAttribute("answers")) {
|
||||
answers = replicaXML.getAttribute("answers");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
String[] ansIDs = answers.split(" ");
|
||||
for (int j = 0; j < ansIDs.length; j++) {
|
||||
int id = Integer.parseInt(ansIDs[j]);
|
||||
allReplicas.get(currentReplicaID).answers.add(allReplicas.get(id));
|
||||
}
|
||||
}
|
||||
listToPopulate.addAll(allReplicas.values());
|
||||
} catch (ParserConfigurationException ex) {
|
||||
Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/64659614/6929164
|
||||
// new empty lines will appear on every XML save without this function
|
||||
private void stripEmptyElements(Node node) {
|
||||
NodeList children = node.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); ++i) {
|
||||
Node child = children.item(i);
|
||||
if (child.getNodeType() == Node.TEXT_NODE) {
|
||||
if (child.getTextContent().trim().length() == 0) {
|
||||
child.getParentNode().removeChild(child);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
stripEmptyElements(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/leylines/dialog/Replica.java
Normal file
@ -0,0 +1,51 @@
|
||||
package leylines.dialog;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Replica implements java.io.Serializable {
|
||||
|
||||
// TODO: Carefully think about ID management and API!! Google it! ID's are important!
|
||||
private static int LAST_ID;
|
||||
|
||||
public final static int OWNER_ME = 0;
|
||||
public final static int OWNER_AI = 1;
|
||||
|
||||
public int owner = OWNER_ME;
|
||||
|
||||
public int id;
|
||||
public String text;
|
||||
public ReplicaActionHandler handler;
|
||||
public List<Replica> answers = new LinkedList<Replica>();
|
||||
|
||||
public Replica(String text) {
|
||||
this.text = text;
|
||||
LAST_ID++; //unlike local variables, static ones are 0 by default, not undefined, so this will work
|
||||
id = LAST_ID;
|
||||
}
|
||||
|
||||
public Replica(String text, int owner, int id) {
|
||||
this(text);
|
||||
this.owner = owner;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getGlobalLastID(){
|
||||
return LAST_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
final Replica other = (Replica) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
10
src/leylines/dialog/ReplicaActionHandler.java
Normal file
@ -0,0 +1,10 @@
|
||||
package leylines.dialog;
|
||||
|
||||
public interface ReplicaActionHandler {
|
||||
|
||||
/**
|
||||
* @return ID of the replica selected by custom script logic
|
||||
*/
|
||||
public int executeScriptLogic();
|
||||
|
||||
}
|
||||
115
src/leylines/dialog/XMLDialogObserver.java
Normal file
@ -0,0 +1,115 @@
|
||||
package leylines.dialog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sova <@graymouse at equestria.social>
|
||||
*/
|
||||
public class XMLDialogObserver {
|
||||
|
||||
private static final XMLDialogObserver instance = new XMLDialogObserver();
|
||||
|
||||
private ObservableList<Replica> replicaList = FXCollections.observableArrayList();
|
||||
|
||||
private File currentlyOpenFile = null;
|
||||
|
||||
// singleton
|
||||
private XMLDialogObserver() {}
|
||||
|
||||
public static XMLDialogObserver getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void printXMLToStream(OutputStream stream) throws ParserConfigurationException{
|
||||
Parser parser = new Parser();
|
||||
parser.printXMlToStream(getXML(), stream);
|
||||
}
|
||||
|
||||
public Document getXML() throws ParserConfigurationException{
|
||||
Parser parser = new Parser();
|
||||
return parser.translateReplicaListToXML(replicaList);
|
||||
}
|
||||
|
||||
public void saveDialogToFile(File file) throws IOException {
|
||||
try {
|
||||
FileOutputStream stream = new FileOutputStream(file);
|
||||
printXMLToStream(stream);
|
||||
stream.close();
|
||||
} catch (ParserConfigurationException ex) {
|
||||
Logger.getLogger(XMLDialogObserver.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}//getTextContent()
|
||||
}
|
||||
|
||||
public void loadReplicaListFromFile(File file) throws IOException, SAXException{
|
||||
Parser parser = new Parser();
|
||||
|
||||
parser.populateReplicaListFromFile(replicaList, file);
|
||||
}
|
||||
|
||||
public List<Replica> getReplicaList(){
|
||||
return replicaList;
|
||||
}
|
||||
|
||||
public Replica getDialog(){
|
||||
return replicaList.get(0);
|
||||
}
|
||||
|
||||
public void add(Replica r){
|
||||
replicaList.add(r);
|
||||
}
|
||||
|
||||
public void remove(Replica r){
|
||||
replicaList.remove(r);
|
||||
}
|
||||
|
||||
public void remove(int index){
|
||||
replicaList.remove(index);
|
||||
}
|
||||
|
||||
public void addAll(Replica... r){
|
||||
replicaList.addAll(r);
|
||||
}
|
||||
|
||||
public void addAll(Collection<? extends Replica> r){
|
||||
replicaList.addAll(r);
|
||||
}
|
||||
|
||||
public void addListener(ListChangeListener<? super Replica> listener){
|
||||
replicaList.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ListChangeListener<? super Replica> listener){
|
||||
replicaList.removeListener(listener);
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
replicaList.clear();
|
||||
}
|
||||
|
||||
|
||||
public void setCurrentFile(File file){
|
||||
currentlyOpenFile = file;
|
||||
}
|
||||
|
||||
public File getCurrentFile(){
|
||||
return currentlyOpenFile;
|
||||
}
|
||||
|
||||
public boolean isFromFile(){
|
||||
return currentlyOpenFile != null;
|
||||
}
|
||||
}
|
||||
10
src/leylines/gui/CustomAlerts.css
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
Created on : Dec 4, 2024, 6:24:46 PM
|
||||
Author : Sova <@graymouse at equestria.social>
|
||||
*/
|
||||
|
||||
.label {
|
||||
-fx-font-style: italic;
|
||||
-fx-font-size: 12;
|
||||
}
|
||||
|
||||
94
src/leylines/gui/LiveSearchEngine.java
Normal file
@ -0,0 +1,94 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch;
|
||||
import me.xdrop.fuzzywuzzy.ToStringFunction;
|
||||
|
||||
/**
|
||||
* @author Sova <@graymouse at equestria.social>
|
||||
* @param <T> type of a data for the Search Engine
|
||||
*/
|
||||
public class LiveSearchEngine<T> implements ChangeListener {
|
||||
|
||||
private int MAX_WORDS_TO_MATCH = 10;
|
||||
|
||||
List<T> allValuesList;
|
||||
ToStringFunction toStringFunction;
|
||||
List<SearchResult> searchResultList; // values with similarity to a sort string more then 50%
|
||||
Runnable callback;
|
||||
|
||||
/**
|
||||
* @param allValuesList the list to search in
|
||||
* @param searchResultsList the list to populate with search results
|
||||
* @param toStringFunction the function that returns comprehensible String from a given type, e.g. for File it should be file name
|
||||
* @param callback optional function to run after the search is finished, useful to update GUI
|
||||
*/
|
||||
public LiveSearchEngine(List<T> allValuesList, List<SearchResult> searchResultsList, ToStringFunction<T> toStringFunction, Runnable callback) {
|
||||
this.allValuesList = allValuesList;
|
||||
this.searchResultList = searchResultsList;
|
||||
this.toStringFunction = toStringFunction;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue ov, Object oldValue, Object newValue) {
|
||||
if(newValue instanceof String searchQuery) {
|
||||
searchResultList.clear();
|
||||
|
||||
if(searchQuery.isBlank()) {
|
||||
for(var item: allValuesList) {
|
||||
searchResultList.add(new SearchResult(100, item));
|
||||
System.out.println(item + "");
|
||||
}
|
||||
} else {
|
||||
for(var item: allValuesList) {
|
||||
String unoptimizedItemStringValue = toStringFunction.apply(item);
|
||||
//String optimizedItemStringValue = optimizeStringForSearch(unoptimizedItemStringValue);
|
||||
int ratio = FuzzySearch.partialRatio(searchQuery, unoptimizedItemStringValue);
|
||||
if(ratio >= 30) searchResultList.add(new SearchResult(ratio, item));
|
||||
}
|
||||
}
|
||||
if(callback != null) callback.run();
|
||||
} else {
|
||||
System.err.println("Caution! LiveSearch engine recieves non-string values, no search perfored.");
|
||||
}
|
||||
}
|
||||
|
||||
private String optimizeStringForSearch(String theLineToPrepare){
|
||||
String result = "";
|
||||
String[] allWords = theLineToPrepare.split(" ");
|
||||
String[] trimmed = Arrays.copyOf(allWords, allWords.length > MAX_WORDS_TO_MATCH ? MAX_WORDS_TO_MATCH : allWords.length);
|
||||
for(int i = 0; i < trimmed.length; i++ ) {
|
||||
result += i < (trimmed.length - 1) ? trimmed[i] + " " : trimmed[i]; // do not add trailing space
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see LiveSearchEngine#setMaxWordsToMatch(int)
|
||||
*/
|
||||
public int getMaxWordsToMatch() {
|
||||
return MAX_WORDS_TO_MATCH;
|
||||
}
|
||||
/**
|
||||
* @param maxWordsToMatch The string is trimmed to avoid comparing too long sequences, set how much here
|
||||
*/
|
||||
public void setMaxWordsToMatch(int maxWordsToMatch) {
|
||||
this.MAX_WORDS_TO_MATCH = maxWordsToMatch;
|
||||
}
|
||||
|
||||
class SearchResult {
|
||||
public int ratio;
|
||||
public T value;
|
||||
|
||||
public SearchResult(int ratio, T value) {
|
||||
this.ratio = ratio;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
101
src/leylines/gui/LoadFromDilalogFolderController.java
Normal file
@ -0,0 +1,101 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import leylines.dialog.Parser;
|
||||
|
||||
/**
|
||||
* @author Sova <@graymouse at equestria.social>
|
||||
*/
|
||||
public class LoadFromDilalogFolderController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private Button cancelButton;
|
||||
|
||||
@FXML
|
||||
private Button confirmButton;
|
||||
|
||||
@FXML
|
||||
private ListView<File> filesList;
|
||||
|
||||
@FXML
|
||||
private VBox contentPane;
|
||||
|
||||
@FXML
|
||||
private TextField searchField;
|
||||
|
||||
@FXML
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
filesList.setCellFactory(param -> new FileListCell());
|
||||
filesList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
contentPane.setUserData(newValue);
|
||||
confirmButton.setDisable(false);
|
||||
}
|
||||
});
|
||||
filesList.getItems().addAll(getFiles(Parser.defaultFolder));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void cancel(){
|
||||
Stage stage = (Stage) contentPane.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void confirm(){
|
||||
// closing the window an passing data is handled in the caller class
|
||||
cancel();
|
||||
}
|
||||
|
||||
private List<File> getFiles(String dirname) {
|
||||
System.out.println("loading from " + dirname);
|
||||
|
||||
if (dirname == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
File dir = new File(dirname);
|
||||
if (!dir.exists()) {
|
||||
System.err.println("no such dir");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (!dir.isDirectory()) {
|
||||
return Collections.singletonList(new File(dirname));
|
||||
}
|
||||
|
||||
return Arrays.stream(Objects.requireNonNull(dir.listFiles()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public class FileListCell extends ListCell<File> {
|
||||
|
||||
@Override
|
||||
public void updateItem(File item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
} else {
|
||||
setText(getItem().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/leylines/gui/Main.java
Normal file
@ -0,0 +1,42 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
// use this:
|
||||
// https://examples.javacodegeeks.com/java-development/desktop-java/javafx/fxml/javafx-fxml-controller-example/#intro_ui
|
||||
public class Main extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
try {
|
||||
// Turn fonts anti-aliacing gray instead of RGB, so fonts do not look jagged
|
||||
System.setProperty("prism.lcdtext", "false");
|
||||
// I dont know what exactly this line does, but fonts look better with it
|
||||
// Reference https://github.com/woky/javafx-hates-linux/blob/master/JavaFxFontsReallySuckOnLinux.java
|
||||
System.setProperty("prism.text", "native");
|
||||
|
||||
Parent root = FXMLLoader.load(getClass().getResource("MainGUI.fxml"));
|
||||
Scene scene = new Scene(root);
|
||||
|
||||
// General CSS for application-wide styles
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
primaryStage.setScene(scene);
|
||||
|
||||
primaryStage.setTitle("Ley Lines Dialog Editor");
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.show();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Cannot read FXML!\n");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
67
src/leylines/gui/MainGUI.css
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
Created on : Nov 3, 2024, 2:33:36 AM
|
||||
Author : Sova
|
||||
*/
|
||||
|
||||
/* useful links
|
||||
* https://www.jensd.de/wordpress/?p=1245
|
||||
* https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#region
|
||||
*/
|
||||
|
||||
.toolbar-button {
|
||||
-fx-font-size: 10;
|
||||
}
|
||||
|
||||
.toolbar-button:focused {
|
||||
-fx-focus-color: #E9967A;
|
||||
-fx-faint-focus-color: #FFEBCD22;
|
||||
}
|
||||
|
||||
#dialog-tree .tree-cell:selected {
|
||||
-fx-background-color: GhostWhite;
|
||||
-fx-text-fill: black ;
|
||||
}
|
||||
#dialog-tree .tree-cell {
|
||||
-fx-wrap-text: true;
|
||||
}
|
||||
|
||||
.tree-cell:selected .tree-disclosure-node .arrow {
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
.replica-label {
|
||||
-fx-font-style: italic;
|
||||
-fx-padding: 0 3px 0 3px;
|
||||
}
|
||||
.ai-label {
|
||||
-fx-text-fill: SALMON;
|
||||
}
|
||||
|
||||
.ending-label{
|
||||
-fx-text-fill: LIGHTSKYBLUE;
|
||||
}
|
||||
|
||||
.id-label {
|
||||
-fx-text-fill: gray;
|
||||
}
|
||||
|
||||
.orphan-label{
|
||||
-fx-text-fill: MEDIUMPURPLE;
|
||||
}
|
||||
|
||||
#dialog-tree:focused {
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
/* Hide horizontal scroll bar */
|
||||
.tree-view .scroll-bar:horizontal {
|
||||
-fx-opacity: 0;
|
||||
-fx-padding:-7;
|
||||
}
|
||||
|
||||
.tree-cell:sub-tree-item {
|
||||
-fx-text-fill: gray;
|
||||
-fx-font-style: italic;
|
||||
}
|
||||
275
src/leylines/gui/MainGUI.fxml
Normal file
@ -0,0 +1,275 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
Copyright (c) 2015, 2019, Gluon and/or its affiliates.
|
||||
All rights reserved. Use is subject to license terms.
|
||||
|
||||
This file is available and licensed under the following license:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
- Neither the name of Oracle Corporation nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.Rectangle2D?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.control.TreeView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox id="root-panel" prefHeight="400.0" prefWidth="640.0" stylesheets="@MainGUI.css" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="leylines.gui.MainGUIController">
|
||||
<children>
|
||||
<ToolBar prefHeight="40.0" prefWidth="200.0" styleClass="toolbar">
|
||||
<items>
|
||||
<HBox spacing="4.0">
|
||||
<children>
|
||||
<Button id="load-project" fx:id="saveButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#saveToDefault" prefHeight="32.0" styleClass="toolbar-button" text="Save" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/floppy-disk.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Save to a default dialog folder">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="save-project" fx:id="loadButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#openFromResourceFolder" prefHeight="32.0" styleClass="toolbar-button" text="Load" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" scaleX="1.1" scaleY="1.1">
|
||||
<image>
|
||||
<Image url="@../res/icons/save.png" />
|
||||
</image>
|
||||
<viewport>
|
||||
<Rectangle2D />
|
||||
</viewport>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Load from standard dialog folder">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="load-project" fx:id="saveAsButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#saveToFile" prefHeight="32.0" styleClass="toolbar-button" text="Save As" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/data-storage.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Save dialog to a certain file">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="save-project" fx:id="importButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#loadFromFile" prefHeight="32.0" styleClass="toolbar-button" text="Load From" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/folder.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Load dialog from any file on PC">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="4.0" left="4.0" right="4.0" top="4.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
</items>
|
||||
</ToolBar>
|
||||
<VBox prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS">
|
||||
<children>
|
||||
<TreeView id="dialog-tree" fx:id="dialogTree" editable="true" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
|
||||
<ToolBar maxHeight="-Infinity" nodeOrientation="LEFT_TO_RIGHT" prefHeight="40.0" styleClass="toolbar">
|
||||
<items>
|
||||
<HBox alignment="CENTER" maxWidth="1.7976931348623157E308" spacing="6.0">
|
||||
<children>
|
||||
<Button id="load-project" fx:id="newReplicaButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#addReplica" prefHeight="32.0" styleClass="toolbar-button" text="New" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/add-task.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Create new Replica">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="save-project" fx:id="editButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#editReplica" prefHeight="32.0" styleClass="toolbar-button" text="Edit" textAlignment="CENTER" HBox.hgrow="SOMETIMES">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/note.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Edit Replica text">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="load-project" fx:id="answersButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#selectAnswers" prefHeight="32.0" styleClass="toolbar-button" text="Answers" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/chat.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Select Answers">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="save-project" fx:id="deleteButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#removeSelectedReplica" prefHeight="32.0" styleClass="toolbar-button" text="Delete" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" scaleX="0.8" scaleY="0.8">
|
||||
<image>
|
||||
<Image url="@../res/icons/cross.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Delete selected">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button id="save-project" fx:id="clearAllButton" focusTraversable="false" maxHeight="-Infinity" mnemonicParsing="false" onAction="#clearDialog" prefHeight="32.0" styleClass="toolbar-button" text="Clear All" textAlignment="CENTER">
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../res/icons/nuclear-explosion.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0" />
|
||||
</padding>
|
||||
<tooltip>
|
||||
<Tooltip text="Remove all Replicas from this Dialog">
|
||||
<font>
|
||||
<Font name="Meiryo UI" size="12.0" />
|
||||
</font>
|
||||
</Tooltip>
|
||||
</tooltip>
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
</items>
|
||||
</ToolBar>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
412
src/leylines/gui/MainGUIController.java
Normal file
@ -0,0 +1,412 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javax.imageio.ImageIO;
|
||||
import leylines.dialog.Replica;
|
||||
import leylines.dialog.XMLDialogObserver;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sova
|
||||
*/
|
||||
public class MainGUIController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private TreeView<Replica> dialogTree;
|
||||
|
||||
@FXML
|
||||
private Button saveButton;
|
||||
|
||||
@FXML
|
||||
private Button loadButton;
|
||||
|
||||
@FXML
|
||||
private Button exportButton;
|
||||
|
||||
@FXML
|
||||
private Button answersButton;
|
||||
|
||||
@FXML
|
||||
private Button clearAllButton;
|
||||
|
||||
@FXML
|
||||
private Button deleteButton;
|
||||
|
||||
@FXML
|
||||
private Button editButton;
|
||||
|
||||
@FXML
|
||||
private Button newReplicaButton;
|
||||
|
||||
@FXML
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
|
||||
dialogTree.setCellFactory(new ReplicaTreeCellFactory());
|
||||
|
||||
XMLDialogObserver replicaList = XMLDialogObserver.getInstance();
|
||||
|
||||
TreeItem<Replica> rootItem = new TreeItem<> (new Replica("Dialog"));
|
||||
rootItem.setExpanded(true);
|
||||
|
||||
dialogTree.setRoot(rootItem);
|
||||
XMLDialogObserver.getInstance().addListener(
|
||||
(ListChangeListener.Change<? extends Replica> change) -> {
|
||||
while (change.next()) {
|
||||
if(change.wasAdded()) {
|
||||
List<? extends Replica> changelist = change.getAddedSubList();
|
||||
changelist.forEach((replica) -> {
|
||||
dialogTree.getRoot().getChildren().add(new TreeItem<>(replica));
|
||||
});
|
||||
if(clearAllButton.isDisabled()) clearAllButton.setDisable(false);
|
||||
}
|
||||
|
||||
if(change.wasRemoved()) {
|
||||
List<? extends Replica> changelist = change.getRemoved();
|
||||
changelist.forEach((replica) -> {
|
||||
dialogTree.getRoot().getChildren().removeIf((elem) -> {
|
||||
return elem.getValue().equals(replica);
|
||||
});
|
||||
});
|
||||
if(rootItem.getChildren().isEmpty()) {
|
||||
if(!clearAllButton.isDisabled()) clearAllButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: replace with default XML Dialog file, so this template dialog can be replaced by user
|
||||
replicaList.addAll(
|
||||
new Replica("Why are thou gay?"),
|
||||
new Replica("I am not"),
|
||||
new Replica("I indeed am and thou should apreciate it")
|
||||
);
|
||||
|
||||
setReplicaTweaksButtonsDisabled(true);
|
||||
dialogTree.getSelectionModel().selectedItemProperty().addListener((v, oldValue, newValue) -> {
|
||||
if((newValue instanceof TreeItem<Replica>)
|
||||
&& newValue != rootItem
|
||||
&& newValue.getParent() == rootItem
|
||||
){
|
||||
setReplicaTweaksButtonsDisabled(false);
|
||||
} else setReplicaTweaksButtonsDisabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void addReplica(ActionEvent event) {
|
||||
// TODO: move default replica text to var
|
||||
XMLDialogObserver.getInstance().add(new Replica("< Double-click me to edit, Enter to save changes.. >"));
|
||||
}
|
||||
|
||||
//https://code.makery.ch/blog/javafx-dialogs-official/
|
||||
@FXML
|
||||
void saveToFile(ActionEvent event) {
|
||||
try {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
|
||||
//Set extension filter for text files
|
||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("XML files (*.xml)", "*.xml");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
|
||||
//Show save file dialog
|
||||
File file = fileChooser.showSaveDialog(dialogTree.getScene().getWindow());
|
||||
|
||||
if (file != null) {
|
||||
XMLDialogObserver.getInstance().saveDialogToFile(file);
|
||||
showSuccessfulSaveMessage(file.getName());
|
||||
setCurrentFile(file);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
showUnsuccessfulSaveMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void loadFromFile(ActionEvent event) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
|
||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("XML files (*.xml)", "*.xml");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
|
||||
File file = fileChooser.showOpenDialog(dialogTree.getScene().getWindow());
|
||||
loadFromFileUpdatingTreeAnswers(file);
|
||||
setCurrentFile(file);
|
||||
}
|
||||
|
||||
private void loadFromFileUpdatingTreeAnswers(File file) {
|
||||
try {
|
||||
if (file != null) {
|
||||
List<Replica> replicaList = XMLDialogObserver.getInstance().getReplicaList();
|
||||
XMLDialogObserver.getInstance().clear();
|
||||
XMLDialogObserver.getInstance().loadReplicaListFromFile(file);
|
||||
|
||||
dialogTree.getRoot().getChildren().forEach((treeItem) -> {
|
||||
for (Replica r : treeItem.getValue().answers) {
|
||||
treeItem.getChildren().add(new TreeItem(r));
|
||||
}
|
||||
});
|
||||
|
||||
dialogTree.getRoot().getChildren().sort((treeItem1, treeItem2) -> {
|
||||
if( treeItem1.getValue().id > treeItem2.getValue().id) return 1;
|
||||
else if( treeItem1.getValue().id == treeItem2.getValue().id ) return 0;
|
||||
else return -1;
|
||||
});
|
||||
}
|
||||
} catch (IOException | SAXException ex) {
|
||||
showUnsuccessfulLoadMessage(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void clearDialog(ActionEvent event) {
|
||||
if(confirmClearMessage()){
|
||||
XMLDialogObserver.getInstance().clear(); // let change listener handle tree update
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void selectAnswers(ActionEvent event) {
|
||||
callAnswersModal(dialogTree.getSelectionModel().getSelectedItem());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void removeSelectedReplica(ActionEvent event) {
|
||||
dialogTree.getSelectionModel().getSelectedItems().forEach((selectedTreeItem)->{
|
||||
Replica toRemoveReplica = selectedTreeItem.getValue();
|
||||
XMLDialogObserver.getInstance().remove(toRemoveReplica);
|
||||
|
||||
//chain remove this one from other replicas answers
|
||||
//NOTE: this method IS duplicating method from tree cell - tree cell cannot be
|
||||
//reached from outside, and tree modification shoudl not happen in XMLDialogObserver so it is here
|
||||
dialogTree.getRoot().getChildren().forEach((currentTreeItem)->{
|
||||
Replica currentReplica = currentTreeItem.getValue();
|
||||
|
||||
if(currentReplica.answers.contains(toRemoveReplica)){
|
||||
currentReplica.answers.remove(toRemoveReplica);
|
||||
currentTreeItem.getChildren().removeIf(child -> {
|
||||
return child.getValue().equals(toRemoveReplica);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void showSuccessfulSaveMessage(String filename){
|
||||
Alert alert = new Alert(AlertType.INFORMATION);
|
||||
alert.setTitle("Save successful!");
|
||||
alert.setHeaderText("The dialog file has been saved in '" + filename + "'!");
|
||||
|
||||
Scene scene = (Scene) alert.getDialogPane().getScene();
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("CustomAlerts.css").toExternalForm());
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showUnsuccessfulSaveMessage(){
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Alert!");
|
||||
alert.setHeaderText("All has broken ser, I repeat, all has broken!");
|
||||
alert.setContentText("Actually its OK, just your file cannot be saved there. Try another folder.");
|
||||
|
||||
Scene scene = (Scene) alert.getDialogPane().getScene();
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("CustomAlerts.css").toExternalForm());
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showUnsuccessfulLoadMessage(Exception e){
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setTitle("Alert!");
|
||||
alert.setHeaderText("All has broken ser, I repeat, all has broken!");
|
||||
if(e instanceof IOException) {
|
||||
alert.setContentText("Something is wrong with the file. Is the programm allowed to read it? Does it exists?");
|
||||
} else if (e instanceof SAXException) {
|
||||
alert.setContentText("File cannot be parsed, error with the markup. Fix the file manually before loading it with editor.");
|
||||
}
|
||||
|
||||
Scene scene = (Scene) alert.getDialogPane().getScene();
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("CustomAlerts.css").toExternalForm());
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private boolean confirmClearMessage(){
|
||||
Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.initOwner(dialogTree.getScene().getWindow());
|
||||
Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
URL myUrl = getClass().getClassLoader().getResource("leylines/res/icons/nuclear-explosion.png");
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(myUrl);
|
||||
stage.getIcons().add(SwingFXUtils.toFXImage(image, null));
|
||||
|
||||
ImageView iv = new ImageView(SwingFXUtils.toFXImage(image, null));
|
||||
iv.setPreserveRatio(true);
|
||||
alert.setGraphic(iv);
|
||||
} catch(IOException e) {
|
||||
System.err.println("cannot read icons!");
|
||||
}
|
||||
alert.setTitle("Clear Dialog");
|
||||
alert.setHeaderText("Do you want to remove all replicas in this dialog?");
|
||||
|
||||
Scene scene = (Scene) alert.getDialogPane().getScene();
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("CustomAlerts.css").toExternalForm());
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
|
||||
return result.get() == ButtonType.OK;
|
||||
}
|
||||
|
||||
private void setReplicaTweaksButtonsDisabled(boolean disabled) {
|
||||
deleteButton.setDisable(disabled);
|
||||
editButton.setDisable(disabled);
|
||||
answersButton.setDisable(disabled);
|
||||
}
|
||||
|
||||
private void callAnswersModal(TreeItem<Replica> treeItem) {
|
||||
XMLDialogObserver observer = XMLDialogObserver.getInstance();
|
||||
Parent root = new SearchAnswerModal(observer.getReplicaList(), treeItem.getValue());
|
||||
Stage modalWindow = new Stage();
|
||||
Scene scene = new Scene(root);
|
||||
// General CSS for application-wide styles
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("modal.css").toExternalForm());
|
||||
modalWindow.setScene(scene);
|
||||
modalWindow.initOwner(dialogTree.getScene().getWindow());
|
||||
modalWindow.initModality(Modality.WINDOW_MODAL);
|
||||
if (treeItem.getValue().text.length() > 100) {
|
||||
modalWindow.setTitle(treeItem.getValue().text.substring(0, 100) + "...");
|
||||
} else {
|
||||
modalWindow.setTitle(treeItem.getValue().text);
|
||||
}
|
||||
modalWindow.setResizable(false);
|
||||
modalWindow.setOnHiding(new EventHandler<WindowEvent>() {
|
||||
@Override
|
||||
public void handle(WindowEvent t) {
|
||||
treeItem.getChildren().clear();
|
||||
treeItem.getValue().answers.sort((replica1, replica2) -> {
|
||||
if (replica1.id > replica2.id) {
|
||||
return 1;
|
||||
} else if (replica1.id == replica2.id) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
for (Replica answer : treeItem.getValue().answers) {
|
||||
treeItem.getChildren().add(new TreeItem(answer));
|
||||
}
|
||||
}
|
||||
});
|
||||
modalWindow.showAndWait();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void editReplica(ActionEvent event) {
|
||||
dialogTree.edit(dialogTree.getSelectionModel().getSelectedItem());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void openFromResourceFolder(ActionEvent event) {
|
||||
try (InputStream in = getClass().getResourceAsStream("loadFromDialogFolderModal.fxml")){
|
||||
FXMLLoader loader = new FXMLLoader();
|
||||
Parent root = loader.load(in);
|
||||
Stage modalWindow = new Stage();
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(this.getClass().getResource("modal.css").toExternalForm());
|
||||
modalWindow.setScene(scene);
|
||||
modalWindow.initOwner(((Node) event.getSource()).getScene().getWindow());
|
||||
modalWindow.initModality(Modality.APPLICATION_MODAL);
|
||||
modalWindow.setTitle("Open file from standard dialog folder");
|
||||
modalWindow.setResizable(false);
|
||||
modalWindow.setOnHiding(new EventHandler<WindowEvent>() {
|
||||
@Override
|
||||
public void handle(WindowEvent t) {
|
||||
var selectedFile = root.getUserData();
|
||||
if(selectedFile instanceof File file) {
|
||||
loadFromFileUpdatingTreeAnswers(file);
|
||||
setCurrentFile(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
modalWindow.showAndWait();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void saveToDefault(ActionEvent event){
|
||||
File currentlyOpenFile = XMLDialogObserver.getInstance().getCurrentFile();
|
||||
if(currentlyOpenFile != null) {
|
||||
try {
|
||||
Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.initOwner(dialogTree.getScene().getWindow());
|
||||
alert.setTitle("Save dialog");
|
||||
alert.setHeaderText("Ready to save to "+currentlyOpenFile.getName()+"?");
|
||||
|
||||
Scene scene = (Scene) alert.getDialogPane().getScene();
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("CustomAlerts.css").toExternalForm());
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
if(result.get() == ButtonType.OK) {
|
||||
XMLDialogObserver.getInstance().saveDialogToFile(currentlyOpenFile);
|
||||
showSuccessfulSaveMessage(currentlyOpenFile.getName());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
showUnsuccessfulSaveMessage();
|
||||
}
|
||||
} else {
|
||||
saveToFile(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCurrentFile(File file){
|
||||
if(file != null) {
|
||||
Stage stage = (Stage) dialogTree.getScene().getWindow();
|
||||
stage.setTitle("Ley Lines Dialog editor: "+ file.getName());
|
||||
XMLDialogObserver.getInstance().setCurrentFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/leylines/gui/ReplicaTreeCellFactory.java
Normal file
@ -0,0 +1,117 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.util.Objects;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.util.Callback;
|
||||
import leylines.dialog.Replica;
|
||||
|
||||
public class ReplicaTreeCellFactory implements Callback<TreeView<Replica>, TreeCell<Replica>> {
|
||||
|
||||
// Reference/Credits https://github.com/cerebrosoft/treeview-dnd-example/blob/master/treedrag/TaskCellFactory.java
|
||||
|
||||
private static final DataFormat JAVA_FORMAT = new DataFormat("application/x-java-serialized-object");
|
||||
private static final String DROP_HINT_STYLE = "-fx-border-color: #eea82f; -fx-border-width: 0 0 2 0; -fx-padding: 3 3 1 3";
|
||||
private TreeCell<Replica> dropZone;
|
||||
private TreeItem<Replica> draggedItem;
|
||||
|
||||
public ReplicaTreeCellFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeCell<Replica> call(TreeView<Replica> treeView) {
|
||||
TreeCell<Replica> cell = new TextFieldTreeCellImpl();
|
||||
cell.setOnDragDetected((MouseEvent event) -> dragDetected(event, cell, treeView));
|
||||
cell.setOnDragOver((DragEvent event) -> dragOver(event, cell, treeView));
|
||||
cell.setOnDragDropped((DragEvent event) -> drop(event, cell, treeView));
|
||||
cell.setOnDragDone((DragEvent event) -> clearDropLocation());
|
||||
|
||||
//limit width to force line wrap on tree items
|
||||
cell.prefWidthProperty().bind(treeView.widthProperty().subtract(5.0));
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
private void dragDetected(MouseEvent event, TreeCell<Replica> treeCell, TreeView<Replica> treeView) {
|
||||
draggedItem = treeCell.getTreeItem();
|
||||
|
||||
if(draggedItem == null) return;
|
||||
|
||||
// root can't be dragged
|
||||
if (draggedItem.getParent() == null) return;
|
||||
|
||||
//answers cannot be dragged
|
||||
if (draggedItem.getParent() != treeView.getRoot()) return;
|
||||
|
||||
Dragboard db = treeCell.startDragAndDrop(TransferMode.MOVE);
|
||||
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.put(JAVA_FORMAT, draggedItem.getValue());
|
||||
db.setContent(content);
|
||||
db.setDragView(treeCell.snapshot(null, null));
|
||||
event.consume();
|
||||
}
|
||||
|
||||
private void dragOver(DragEvent event, TreeCell<Replica> treeCell, TreeView<Replica> treeView) {
|
||||
if (!event.getDragboard().hasContent(JAVA_FORMAT)) return;
|
||||
TreeItem<Replica> thisItem = treeCell.getTreeItem();
|
||||
|
||||
// can't drop on itself
|
||||
if (draggedItem == null || thisItem == null || thisItem == draggedItem) return;
|
||||
// ignore if this is the root
|
||||
if (draggedItem.getParent() == null) {
|
||||
clearDropLocation();
|
||||
return;
|
||||
}
|
||||
// do not allow dragging to the other replica answer list
|
||||
if (thisItem.getParent() != treeView.getRoot()) {
|
||||
clearDropLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
event.acceptTransferModes(TransferMode.MOVE);
|
||||
if (!Objects.equals(dropZone, treeCell)) {
|
||||
clearDropLocation();
|
||||
this.dropZone = treeCell;
|
||||
dropZone.setStyle(DROP_HINT_STYLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void drop(DragEvent event, TreeCell<Replica> treeCell, TreeView<Replica> treeView) {
|
||||
Dragboard db = event.getDragboard();
|
||||
boolean success = false;
|
||||
if (!db.hasContent(JAVA_FORMAT)) return;
|
||||
|
||||
TreeItem<Replica> thisItem = treeCell.getTreeItem();
|
||||
TreeItem<Replica> droppedItemParent = draggedItem.getParent();
|
||||
|
||||
// remove from previous location
|
||||
droppedItemParent.getChildren().remove(draggedItem);
|
||||
|
||||
// dropping on parent node makes it the first child
|
||||
if (Objects.equals(droppedItemParent, thisItem)) {
|
||||
thisItem.getChildren().add(0, draggedItem);
|
||||
treeView.getSelectionModel().select(draggedItem);
|
||||
}
|
||||
else {
|
||||
// add to new location
|
||||
int indexInParent = thisItem.getParent().getChildren().indexOf(thisItem);
|
||||
thisItem.getParent().getChildren().add(indexInParent + 1, draggedItem);
|
||||
}
|
||||
treeView.getSelectionModel().select(draggedItem);
|
||||
event.setDropCompleted(success);
|
||||
}
|
||||
|
||||
private void clearDropLocation() {
|
||||
if (dropZone != null) dropZone.setStyle("");
|
||||
}
|
||||
}
|
||||
154
src/leylines/gui/SearchAnswerModal.java
Normal file
@ -0,0 +1,154 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import leylines.dialog.Replica;
|
||||
import me.xdrop.fuzzywuzzy.ToStringFunction;
|
||||
|
||||
/**
|
||||
* @author Sova <@graymouse at equestria.social>
|
||||
*/
|
||||
public class SearchAnswerModal extends VBox {
|
||||
|
||||
// GUI elements
|
||||
private TextField searchBox = new TextField();
|
||||
private ListView<Replica> answersListView = new ListView<>();
|
||||
private HBox bottomBar = new HBox();
|
||||
private Button acceptButton = new Button("Accept");
|
||||
private Button cancelButton = new Button("Cancel");
|
||||
|
||||
// Data fields
|
||||
private List<Replica> searchScope;
|
||||
private Replica answerTo;
|
||||
private List<Replica> selectedAnswersList = new LinkedList<>();
|
||||
private final int MAX_ITEM_TEXT_LENGTH = 100;
|
||||
|
||||
// Live Search
|
||||
List<LiveSearchEngine<Replica>.SearchResult> searchResults = new LinkedList<>();
|
||||
LiveSearchEngine<Replica> liveSearchEngine;
|
||||
|
||||
public SearchAnswerModal(List<Replica> searchScope, Replica answerTo) {
|
||||
this.searchScope = searchScope;
|
||||
this.answerTo = answerTo;
|
||||
|
||||
initSearchBox();
|
||||
initReplicaList();
|
||||
initCancelButton();
|
||||
initAcceptButton();
|
||||
|
||||
// TODO: detect existing answers for answerTo and set auto
|
||||
|
||||
setPrefSize(400, 250);
|
||||
bottomBar.getChildren().addAll(acceptButton, cancelButton);
|
||||
bottomBar.alignmentProperty().set(Pos.CENTER_RIGHT);
|
||||
bottomBar.setId("bottom-bar");
|
||||
this.getChildren().add(bottomBar);
|
||||
}
|
||||
|
||||
private void initSearchBox() {
|
||||
searchBox.setPromptText("Search the list.."); //to set the hint text
|
||||
this.getChildren().add(searchBox);
|
||||
|
||||
Runnable callback = () -> {
|
||||
answersListView.getItems().clear();
|
||||
searchResults.forEach((item)->{
|
||||
answersListView.getItems().add(item.value);
|
||||
});
|
||||
};
|
||||
|
||||
ToStringFunction<Replica> tsf = (Replica item) -> item.text;
|
||||
|
||||
liveSearchEngine = new LiveSearchEngine(searchScope, searchResults, tsf, callback);
|
||||
searchBox.textProperty().addListener(liveSearchEngine);
|
||||
}
|
||||
|
||||
private void initReplicaList() {
|
||||
|
||||
answersListView.getItems().addAll(searchScope);
|
||||
answersListView.getItems().remove(answerTo); // do not show current item, it cannot be answer to itself
|
||||
|
||||
// select all already existing answers to avoid adding duplicates
|
||||
selectedAnswersList.addAll(answerTo.answers);
|
||||
|
||||
answersListView.setCellFactory(param -> {
|
||||
ReplicaListCell rlc = new ReplicaListCell();
|
||||
|
||||
rlc.setSelectedStateCallback((Replica replica) -> {
|
||||
BooleanProperty observable = new SimpleBooleanProperty();
|
||||
|
||||
if(answerTo.answers.contains(rlc.getItem())){
|
||||
observable.set(true);
|
||||
}
|
||||
|
||||
observable.addListener((obs, wasSelected, isNowSelected) -> {
|
||||
if (!wasSelected && isNowSelected) {
|
||||
selectedAnswersList.add(replica);
|
||||
} else if (wasSelected && !isNowSelected) {
|
||||
selectedAnswersList.remove(replica);
|
||||
}
|
||||
});
|
||||
return observable;
|
||||
});
|
||||
return rlc;
|
||||
});
|
||||
|
||||
this.getChildren().add(answersListView);
|
||||
answersListView.requestFocus(); // focus list not the search field so the hint would show
|
||||
}
|
||||
|
||||
public class ReplicaListCell extends CheckBoxListCell<Replica> {
|
||||
|
||||
@Override
|
||||
public void updateItem(Replica item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
setText(null);
|
||||
} else {
|
||||
// force line wrap
|
||||
setMinWidth(this.getWidth());
|
||||
setMaxWidth(this.getWidth());
|
||||
setPrefWidth(this.getWidth());
|
||||
|
||||
setWrapText(true);
|
||||
|
||||
// replace long text
|
||||
if (item.text.length() > MAX_ITEM_TEXT_LENGTH) {
|
||||
setText(item.text.substring(0, MAX_ITEM_TEXT_LENGTH) + "...");
|
||||
} else {
|
||||
setText(item.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initCancelButton(){
|
||||
cancelButton.setOnAction(event -> {
|
||||
Stage stage = (Stage) this.getScene().getWindow();
|
||||
stage.close();
|
||||
});
|
||||
}
|
||||
|
||||
private void initAcceptButton(){
|
||||
acceptButton.setOnAction(event -> {
|
||||
answerTo.answers.clear();
|
||||
answerTo.answers.addAll(selectedAnswersList);
|
||||
|
||||
// TODO: GUI UPDATE LOGIC
|
||||
|
||||
Stage stage = (Stage) this.getScene().getWindow();
|
||||
stage.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
336
src/leylines/gui/TextFieldTreeCellImpl.java
Normal file
@ -0,0 +1,336 @@
|
||||
package leylines.gui;
|
||||
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import leylines.dialog.Replica;
|
||||
import leylines.dialog.XMLDialogObserver;
|
||||
|
||||
/* === TEST CODE STOLEN FROM THE INTERNET === */
|
||||
// https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm#BABEJCHA
|
||||
class TextFieldTreeCellImpl extends TreeCell<Replica> {
|
||||
|
||||
private TextField textField;
|
||||
private Label idLabel = new Label();
|
||||
private Label AILabel = new Label("AI");
|
||||
private Label endingLabel = new Label("END");
|
||||
private Label orphanLabel = new Label("ORPHAN");
|
||||
private HBox tagsBox = new HBox();
|
||||
private ContextMenu replicaContextMenu = new ContextMenu();
|
||||
|
||||
|
||||
public TextFieldTreeCellImpl() {
|
||||
MenuItem editReplicaMenuItem = new MenuItem("Edit Replica");
|
||||
replicaContextMenu.getItems().add(editReplicaMenuItem);
|
||||
editReplicaMenuItem.setOnAction((t) -> startEdit());
|
||||
|
||||
MenuItem selectAnswersReplicaMenuItem = new MenuItem("Manage Answers");
|
||||
replicaContextMenu.getItems().add(selectAnswersReplicaMenuItem);
|
||||
|
||||
MenuItem setAIMenuItem = new MenuItem("Mark as AI");
|
||||
replicaContextMenu.getItems().add(setAIMenuItem);
|
||||
setAIMenuItem.setOnAction((evt)->{
|
||||
if(getItem() != null) { //the item MIGHT be null if clicked too fast
|
||||
if(getItem().owner == Replica.OWNER_ME){
|
||||
getItem().owner = Replica.OWNER_AI;
|
||||
tagsBox.getChildren().add(AILabel);
|
||||
} else {
|
||||
getItem().owner = Replica.OWNER_ME;
|
||||
tagsBox.getChildren().remove(AILabel);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem deleteReplicaMenuItem = new MenuItem("Remove");
|
||||
replicaContextMenu.getItems().add(deleteReplicaMenuItem);
|
||||
deleteReplicaMenuItem.setOnAction((event) -> {
|
||||
if (getTreeItem() == null) return;
|
||||
Replica toRemoveReplica = getTreeItem().getValue();
|
||||
XMLDialogObserver.getInstance().remove(toRemoveReplica);
|
||||
|
||||
//chain remove this one from other replicas answers
|
||||
getTreeView().getRoot().getChildren().forEach((treeItem)->{
|
||||
Replica currentReplica = treeItem.getValue();
|
||||
|
||||
if(currentReplica.answers.contains(toRemoveReplica)){
|
||||
currentReplica.answers.remove(toRemoveReplica);
|
||||
treeItem.getChildren().removeIf(child -> {
|
||||
return child.getValue().equals(toRemoveReplica);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//create modal window
|
||||
selectAnswersReplicaMenuItem.setOnAction((event) -> {
|
||||
if(getItem() == null) return;
|
||||
|
||||
XMLDialogObserver observer = XMLDialogObserver.getInstance();
|
||||
Parent root = new SearchAnswerModal(observer.getReplicaList(), getItem());
|
||||
Stage modalWindow = new Stage();
|
||||
Scene scene = new Scene(root);
|
||||
// General CSS for application-wide styles
|
||||
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
|
||||
scene.getStylesheets().add(getClass().getResource("modal.css").toExternalForm());
|
||||
modalWindow.setScene(scene);
|
||||
modalWindow.initOwner(getTreeView().getScene().getWindow());
|
||||
modalWindow.initModality(Modality.WINDOW_MODAL);
|
||||
if (getItem().text.length() > 100) {
|
||||
modalWindow.setTitle(getItem().text.substring(0, 100) + "...");
|
||||
} else {
|
||||
modalWindow.setTitle(getItem().text);
|
||||
}
|
||||
modalWindow.setResizable(false);
|
||||
modalWindow.setOnHiding(new EventHandler<WindowEvent>() {
|
||||
@Override
|
||||
public void handle(WindowEvent t) {
|
||||
getTreeItem().getChildren().clear();
|
||||
getItem().answers.sort((replica1, replica2) -> {
|
||||
if( replica1.id > replica2.id ) return 1;
|
||||
else if( replica1.id == replica2.id ) return 0;
|
||||
else return -1;
|
||||
});
|
||||
for(Replica answer: getItem().answers) {
|
||||
getTreeItem().getChildren().add(new TreeItem(answer));
|
||||
}
|
||||
}
|
||||
});
|
||||
modalWindow.showAndWait();
|
||||
});
|
||||
|
||||
idLabel.getStyleClass().addAll("replica-label", "id-label");
|
||||
AILabel.getStyleClass().addAll("replica-label", "ai-label");
|
||||
endingLabel.getStyleClass().addAll("replica-label", "ending-label");
|
||||
orphanLabel.getStyleClass().addAll("replica-label", "orphan-label");
|
||||
|
||||
// ID label is always available, other labels might be added on demand
|
||||
tagsBox.getChildren().add(idLabel);
|
||||
|
||||
treeItemProperty().addListener((ObservableValue<? extends TreeItem<Replica>> ov, TreeItem<Replica> oldValue, TreeItem<Replica> newValue) -> {
|
||||
if(newValue != null) {
|
||||
// Tree Item is not available right away and is initalized later
|
||||
// using listener to call "belated" constructor initalize()
|
||||
initalize(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initalize(TreeItem<Replica> currentItem){
|
||||
if( !isEditing() ) {
|
||||
setGraphic(tagsBox);
|
||||
actualizeTags();
|
||||
}
|
||||
}
|
||||
|
||||
private void actualizeTags(){
|
||||
actualizeIDLabel();
|
||||
actualizeAILabel();
|
||||
actualizeENDLabel();
|
||||
actualizeORPHANLabel();
|
||||
}
|
||||
|
||||
private void actualizeIDLabel(){
|
||||
if (getItem() == null) return;
|
||||
|
||||
idLabel.setText("ID:" + getItem().id);
|
||||
|
||||
if( isRoot() ) {
|
||||
if( hasLabel(idLabel) ) removeLabel(idLabel);
|
||||
} else {
|
||||
if( !hasLabel(idLabel) ) addLabel(idLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void actualizeENDLabel(){
|
||||
if (getItem() == null) return;
|
||||
|
||||
if(isRoot()){
|
||||
if(hasLabel(endingLabel)) removeLabel(endingLabel);
|
||||
} else if (isFirstLevelChild()) {
|
||||
if( getItem().answers.isEmpty() && !hasLabel(endingLabel)) addLabel(endingLabel);
|
||||
else if(!getItem().answers.isEmpty() && hasLabel(endingLabel)) removeLabel(endingLabel);
|
||||
} else {
|
||||
if(hasLabel(endingLabel)) removeLabel(endingLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void actualizeAILabel(){
|
||||
if (getItem() == null) return;
|
||||
|
||||
if(isRoot()){
|
||||
if(hasLabel(AILabel)) removeLabel(AILabel);
|
||||
} else if (isFirstLevelChild()) {
|
||||
if( getItem().owner == Replica.OWNER_AI && !hasLabel(AILabel) ) addLabel(AILabel);
|
||||
else if(getItem().owner == Replica.OWNER_ME && hasLabel(AILabel)) removeLabel(AILabel);
|
||||
} else {
|
||||
if(hasLabel(AILabel)) removeLabel(AILabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void actualizeORPHANLabel(){
|
||||
if (getItem() == null) return;
|
||||
|
||||
if(isRoot()){
|
||||
if(hasLabel(orphanLabel)) removeLabel(orphanLabel);
|
||||
} else if (isFirstLevelChild()) {
|
||||
if( isOrphan() && !hasLabel(orphanLabel) ) addLabel(orphanLabel);
|
||||
else if(!isOrphan() && hasLabel(orphanLabel)) removeLabel(orphanLabel);
|
||||
} else {
|
||||
if(hasLabel(orphanLabel)) removeLabel(orphanLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
if (getTreeItem().getParent() == null) {
|
||||
return;// dont edit root
|
||||
} else if(getTreeItem().getParent() != getTreeView().getRoot()){
|
||||
return;// dont edit answers - only direct tree root children i.e. replicas can be edited
|
||||
}
|
||||
super.startEdit();
|
||||
|
||||
if (textField == null) {
|
||||
createTextField();
|
||||
}
|
||||
setText(null);
|
||||
setGraphic(textField);
|
||||
textField.selectAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
setText((String) getItem().text);
|
||||
setGraphic(tagsBox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(Replica item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
if (isEditing()) {
|
||||
if (textField != null) {
|
||||
textField.setText(getString());
|
||||
}
|
||||
setText(null);
|
||||
setGraphic(textField);
|
||||
} else {
|
||||
TreeItem currentItem = getTreeItem();
|
||||
TreeItem rootItem = getTreeView().getRoot();
|
||||
|
||||
setText(getString());
|
||||
setGraphic(tagsBox);
|
||||
|
||||
if (currentItem != null && currentItem.getParent() != null) {
|
||||
// allow only direct dialog children to have context menu, no answers allowed to have answers of their own
|
||||
if(isFirstLevelChild()) {
|
||||
setContextMenu(replicaContextMenu);
|
||||
pseudoClassStateChanged(PseudoClass.getPseudoClass("sub-tree-item"), false);
|
||||
} else {
|
||||
//set lower level tree items class for styling
|
||||
pseudoClassStateChanged(PseudoClass.getPseudoClass("sub-tree-item"), true);
|
||||
// do not show AI label for lower level items for better readability
|
||||
}
|
||||
} else {
|
||||
setContextMenu(null);
|
||||
}
|
||||
|
||||
actualizeTags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createTextField() {
|
||||
textField = new TextField(getString());
|
||||
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
|
||||
|
||||
@Override
|
||||
public void handle(KeyEvent t) {
|
||||
if (t.getCode() == KeyCode.ENTER) {
|
||||
getItem().text = textField.getText();
|
||||
commitEdit(getItem());
|
||||
} else if (t.getCode() == KeyCode.ESCAPE) {
|
||||
cancelEdit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isFirstLevelChild() {
|
||||
return getTreeItem().getParent() == getTreeView().getRoot();
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
return getTreeItem() == getTreeView().getRoot();
|
||||
}
|
||||
|
||||
public boolean isOrphan(){
|
||||
if (getItem() == null) {
|
||||
System.err.println("isOrphan() is called on null");
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isRoot() || !isFirstLevelChild()) {
|
||||
return false; //answers to replicas and root are never orphans
|
||||
}
|
||||
|
||||
//do not return ORPHAN for the first tree child, given that it is a dialog head
|
||||
//TODO: consider making first replica root instead of abstract unusable "dialog" node
|
||||
if(getTreeItem() == getTreeView().getRoot().getChildren().get(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Replica> replicaList = XMLDialogObserver.getInstance().getReplicaList();
|
||||
for(int i = 0; i < replicaList.size(); i++) {
|
||||
if(replicaList.get(i) != getItem() && replicaList.get(i).answers.contains(getItem())){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasLabel(Label label){
|
||||
return tagsBox.getChildren().contains(label);
|
||||
}
|
||||
|
||||
private void addLabel(Label label){
|
||||
tagsBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
private void removeLabel(Label label){
|
||||
tagsBox.getChildren().remove(label);
|
||||
}
|
||||
|
||||
public boolean getIsEditable() {
|
||||
return isEditable();
|
||||
}
|
||||
|
||||
public void setIsEditable(boolean isEditable) {
|
||||
setEditable(isEditable);
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
return getItem() == null ? "" : getItem().text;
|
||||
}
|
||||
|
||||
}
|
||||
/* ========================================== */
|
||||
37
src/leylines/gui/application.css
Normal file
@ -0,0 +1,37 @@
|
||||
/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */
|
||||
|
||||
@font-face {
|
||||
font-family: "Montserrat Alternates";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("../res/fonts/Montserrat_Alternates/MontserratAlternates-Regular.ttf");
|
||||
}
|
||||
|
||||
.root{
|
||||
-fx-font-family: "Montserrat Alternates";
|
||||
-fx-font-smoothing-type: gray;
|
||||
-fx-font-size: 12;
|
||||
-fx-cell-focus-inner-border: transparent; /*removes focus outline */
|
||||
}
|
||||
|
||||
.button{
|
||||
-fx-font-family: "Montserrat Alternates";
|
||||
-fx-font-smoothing-type: gray;
|
||||
-fx-font-size: 12;
|
||||
}
|
||||
|
||||
.text-field:focused {
|
||||
-fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
|
||||
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
-fx-text-fill: gray;
|
||||
-fx-background-color: IVORY;
|
||||
-fx-background-radius: 3;
|
||||
-fx-show-delay: 250ms;
|
||||
-fx-font-family: "Montserrat Alternates";
|
||||
-fx-font-smoothing-type: gray;
|
||||
-fx-font-size: 10;
|
||||
-fx-padding: 5px 15px 5px 15px;
|
||||
}
|
||||
21
src/leylines/gui/loadFromDialogFolderModal.fxml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
|
||||
<VBox fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="250.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/22" xmlns:fx="http://javafx.com/fxml/1" fx:controller="leylines.gui.LoadFromDilalogFolderController">
|
||||
<children>
|
||||
<TextField fx:id="searchField" promptText="Search here.." />
|
||||
<ListView fx:id="filesList" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
|
||||
<HBox id="bottom-bar" alignment="TOP_RIGHT" prefHeight="28.0" prefWidth="400.0" VBox.vgrow="ALWAYS">
|
||||
<children>
|
||||
<Button fx:id="confirmButton" disable="true" mnemonicParsing="false" onAction="#confirm" prefWidth="50.0" text="OK" />
|
||||
<Button fx:id="cancelButton" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
19
src/leylines/gui/modal.css
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Created on : Nov 21, 2024, 4:08:45 PM
|
||||
Author : Sova <@graymouse at equestria.social>
|
||||
*/
|
||||
|
||||
#bottom-bar {
|
||||
-fx-padding: 8;
|
||||
-fx-spacing: 8;
|
||||
}
|
||||
|
||||
.list-view:focused {
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.list-cell{
|
||||
-fx-padding: 5px;
|
||||
}
|
||||
6
src/leylines/res/CREDITS
Normal file
@ -0,0 +1,6 @@
|
||||
floppy-disk.png icon by DinosoftLabs ( https://www.flaticon.com/free-icons/floppy-disk )
|
||||
add-task.png icon by Freepik ( https://www.flaticon.com/free-icons/add-task )
|
||||
documentation.png icon created by Awicon ( https://www.flaticon.com/free-icons/document )
|
||||
folder.png icon created by Freepik ( https://www.flaticon.com/free-icons/folder )
|
||||
save.png icon created by Freepik ( https://www.flaticon.com/free-icons/save )
|
||||
data-storage.png created by juicy_fish ( https://www.flaticon.com/free-icons/save )
|
||||
93
src/leylines/res/fonts/Montserrat_Alternates/OFL.txt
Normal file
@ -0,0 +1,93 @@
|
||||
Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
src/leylines/res/icons/add-task.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/leylines/res/icons/chat.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/leylines/res/icons/computer.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/leylines/res/icons/computer2.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/leylines/res/icons/cross.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
src/leylines/res/icons/data-storage.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/leylines/res/icons/floppy-disk.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
src/leylines/res/icons/folder.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/leylines/res/icons/note.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/leylines/res/icons/nuclear-bomb-128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/leylines/res/icons/nuclear-bomb.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/leylines/res/icons/nuclear-explosion.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/leylines/res/icons/save.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/leylines/res/icons/supernova.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
207
src/test/Controller.java
Normal file
@ -0,0 +1,207 @@
|
||||
package test;
|
||||
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Controller {
|
||||
static Stage stage;
|
||||
static Scene scene;
|
||||
private DataInputStream _DIS;
|
||||
private static DataOutputStream _DOS;
|
||||
|
||||
@FXML// w w w. d e m o 2 s . co m
|
||||
public void loginOnClick() {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
HashMap<String, Boolean> send_to = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TextField loginTF = (TextField) scene.lookup("#login_username");
|
||||
TextField serverIP_TF = (TextField) scene.lookup("#login_server_ip");
|
||||
PasswordField passwordTF = (PasswordField) scene.lookup("#login_password");
|
||||
String serverIP = serverIP_TF.getText();
|
||||
String login = loginTF.getText();
|
||||
String password = passwordTF.getText();
|
||||
Socket socket;
|
||||
int port = 2170;
|
||||
InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(serverIP);
|
||||
try {
|
||||
socket = new Socket(inetAddress, port);
|
||||
socket.setSoTimeout(0);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||
String loginData = "l\n";
|
||||
loginData += login + "\n";
|
||||
loginData += password + "\n";
|
||||
dataOutputStream.writeUTF(loginData);
|
||||
String answer = dataInputStream.readUTF();
|
||||
if (answer.equals("l-ok")) {
|
||||
_DIS = dataInputStream;
|
||||
_DOS = dataOutputStream;
|
||||
Parent root = FXMLLoader.load(getClass().getResource("Chat.fxml"));
|
||||
scene = new Scene(root);
|
||||
Platform.runLater(() -> stage.setScene(scene));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
ObservableList<String> users;
|
||||
ListView listView = (ListView) scene.lookup("#ChatLV");
|
||||
ListView listViewUser = (ListView) scene.lookup("#selectUserLV");
|
||||
listViewUser.setCellFactory(CheckBoxListCell
|
||||
.forListView((Callback<String, ObservableValue<Boolean>>) item -> {
|
||||
BooleanProperty observable = new SimpleBooleanProperty();
|
||||
observable.addListener((obs, wasSelected, isNowSelected) -> {
|
||||
send_to.replace(item, !send_to.get(item));
|
||||
StringBuilder out = new StringBuilder("t");
|
||||
for (int i = 0; i < listViewUser.getItems().size(); i++) {
|
||||
if (send_to.get(listViewUser.getItems().get(i)))
|
||||
out.append("\n").append(listViewUser.getItems().get(i)
|
||||
.toString().split("")[0]);
|
||||
}
|
||||
try {
|
||||
_DOS.writeUTF(out.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("Check box for " + item + " changed from "
|
||||
+ wasSelected + " to " + isNowSelected);
|
||||
});
|
||||
return observable;
|
||||
}));
|
||||
ObservableList<String> items = FXCollections.observableArrayList();
|
||||
|
||||
while (true) {
|
||||
final String msg = _DIS.readUTF();
|
||||
switch (msg.split("\n")[0]) {
|
||||
case "m":
|
||||
items.add(msg.split("\n")[2] + "[" + msg.split("\n")[1] + "]:\n"
|
||||
+ msg.split("\n")[3]);
|
||||
listView.setItems(items);
|
||||
break;
|
||||
case "u":
|
||||
users = FXCollections.observableArrayList();
|
||||
send_to = new HashMap<>();
|
||||
for (int i = 1; i < msg.split("\n").length; i++) {
|
||||
users.add(msg.split("\n")[i]);
|
||||
send_to.put(msg.split("\n")[i], false);
|
||||
}
|
||||
listViewUser.setItems(users);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void sendMessageOnClick() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String msg = "m\n" + ((TextField) scene.lookup("#textTF")).getText();
|
||||
_DOS.writeUTF(msg);
|
||||
((TextField) scene.lookup("#textTF")).setText("");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void signUpOnClick() {
|
||||
new Thread(() -> {
|
||||
int port = 2170;
|
||||
Socket socket;
|
||||
InetAddress inetAddress;
|
||||
TextField loginTF = (TextField) scene.lookup("#sign_up_username");
|
||||
TextField serverIP_TF = (TextField) scene.lookup("#sign_up_server_ip");
|
||||
TextField passwordTF = (TextField) scene.lookup("#sign_up_password");
|
||||
TextField confirmPasswordTF = (TextField) scene.lookup("#sign_up_cpassword");
|
||||
Text error = (Text) scene.lookup("#sign_up_error");
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(serverIP_TF.getText());
|
||||
socket = new Socket(inetAddress, port);
|
||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||
String signUpData = "r\n";
|
||||
signUpData += loginTF.getText() + "\n";
|
||||
if (!signUpData.contains(" ")) {
|
||||
String signUpPassword = passwordTF.getText();
|
||||
if (signUpPassword.length() > 3 && signUpPassword.length() < 33) {
|
||||
if (!signUpPassword.contains(" ")) {
|
||||
if (signUpPassword.equals(confirmPasswordTF.getText())) {
|
||||
signUpData += signUpPassword + "\n";
|
||||
dataOutputStream.writeUTF(signUpData);
|
||||
String answer = dataInputStream.readUTF();
|
||||
if (answer.equals("s-ok")) {
|
||||
error.setText("test test test?.");
|
||||
} else {
|
||||
error.setText("????? ????? ??? test?.");
|
||||
}
|
||||
} else {
|
||||
error.setText("??? test ??????.");
|
||||
}
|
||||
} else {
|
||||
error.setText("?????? ?? test test? test.");
|
||||
}
|
||||
} else {
|
||||
error.setText("test?? test ??????.");
|
||||
}
|
||||
} else {
|
||||
error.setText("????? ?? test test? test.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
error.setText("?????? test.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void openLogin() throws IOException {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("LogIn.fxml"));
|
||||
scene = new Scene(root);
|
||||
Platform.runLater(() -> stage.setScene(scene));
|
||||
}
|
||||
|
||||
public void openSignUp() throws IOException {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("SignUp.fxml"));
|
||||
scene = new Scene(root);
|
||||
Platform.runLater(() -> stage.setScene(scene));
|
||||
}
|
||||
}
|
||||
|
||||
89
src/test/ListViewWithCheckBox.java
Normal file
@ -0,0 +1,89 @@
|
||||
package test;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
|
||||
//https://stackoverflow.com/questions/28843858/javafx-8-listview-with-checkboxes
|
||||
public class ListViewWithCheckBox extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
ListView<Item> listView = new ListView<>();
|
||||
for (int i=1; i<=20; i++) {
|
||||
Item item = new Item("Item "+i, false);
|
||||
|
||||
// observe item's on property and display message if it changes:
|
||||
item.onProperty().addListener((obs, wasOn, isNowOn) -> {
|
||||
System.out.println(item.getName() + " changed on state from "+wasOn+" to "+isNowOn);
|
||||
});
|
||||
|
||||
listView.getItems().add(item);
|
||||
}
|
||||
|
||||
listView.setCellFactory(CheckBoxListCell.forListView(new Callback<Item, ObservableValue<Boolean>>() {
|
||||
@Override
|
||||
public ObservableValue<Boolean> call(Item item) {
|
||||
return item.onProperty();
|
||||
}
|
||||
}));
|
||||
|
||||
BorderPane root = new BorderPane(listView);
|
||||
Scene scene = new Scene(root, 250, 400);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
private final StringProperty name = new SimpleStringProperty();
|
||||
private final BooleanProperty on = new SimpleBooleanProperty();
|
||||
|
||||
public Item(String name, boolean on) {
|
||||
setName(name);
|
||||
setOn(on);
|
||||
}
|
||||
|
||||
public final StringProperty nameProperty() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return this.nameProperty().get();
|
||||
}
|
||||
|
||||
public final void setName(final String name) {
|
||||
this.nameProperty().set(name);
|
||||
}
|
||||
|
||||
public final BooleanProperty onProperty() {
|
||||
return this.on;
|
||||
}
|
||||
|
||||
public final boolean isOn() {
|
||||
return this.onProperty().get();
|
||||
}
|
||||
|
||||
public final void setOn(final boolean on) {
|
||||
this.onProperty().set(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
73
src/test/SerializeReplica.java
Normal file
@ -0,0 +1,73 @@
|
||||
package test;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import leylines.dialog.Replica;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sova
|
||||
*/
|
||||
public class SerializeReplica {
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
Replica replica = new Replica("hehe haha tis is replica");
|
||||
String filename = "file";
|
||||
|
||||
try
|
||||
{
|
||||
//Saving of object in a file
|
||||
FileOutputStream file = new FileOutputStream(filename);
|
||||
ObjectOutputStream out = new ObjectOutputStream(file);
|
||||
|
||||
// Method for serialization of object
|
||||
out.writeObject(replica);
|
||||
|
||||
out.close();
|
||||
file.close();
|
||||
|
||||
System.out.println("Object has been serialized");
|
||||
|
||||
}
|
||||
|
||||
catch(IOException ex)
|
||||
{
|
||||
System.out.println("IOException is caught");
|
||||
}
|
||||
|
||||
|
||||
Replica replica1 = null;
|
||||
|
||||
// Deserialization
|
||||
try
|
||||
{
|
||||
// Reading the object from a file
|
||||
FileInputStream file = new FileInputStream(filename);
|
||||
ObjectInputStream in = new ObjectInputStream(file);
|
||||
|
||||
// Method for deserialization of object
|
||||
replica1 = (Replica)in.readObject();
|
||||
|
||||
in.close();
|
||||
file.close();
|
||||
|
||||
System.out.println("Object has been deserialized ");
|
||||
System.out.println("replica text = " + replica1.text);
|
||||
}
|
||||
|
||||
catch(IOException ex)
|
||||
{
|
||||
System.out.println("IOException is caught");
|
||||
}
|
||||
|
||||
catch(ClassNotFoundException ex)
|
||||
{
|
||||
System.out.println("ClassNotFoundException is caught");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
177
src/test/TreeViewSample.java
Normal file
@ -0,0 +1,177 @@
|
||||
package test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.application.Application;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class TreeViewSample extends Application {
|
||||
|
||||
List<Employee> employees = Arrays.<Employee>asList(
|
||||
new Employee("Ethan Williams", "Sales Department"),
|
||||
new Employee("Emma Jones", "Sales Department"),
|
||||
new Employee("Michael Brown", "Sales Department"),
|
||||
new Employee("Anna Black", "Sales Department"),
|
||||
new Employee("Rodger York", "Sales Department"),
|
||||
new Employee("Susan Collins", "Sales Department"),
|
||||
new Employee("Mike Graham", "IT Support"),
|
||||
new Employee("Judy Mayer", "IT Support"),
|
||||
new Employee("Gregory Smith", "IT Support"),
|
||||
new Employee("Jacob Smith", "Accounts Department"),
|
||||
new Employee("Isabella Johnson", "Accounts Department"));
|
||||
TreeItem<String> rootNode =
|
||||
new TreeItem<String>("MyCompany Human Resources");
|
||||
|
||||
public static void main(String[] args) {
|
||||
Application.launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
rootNode.setExpanded(true);
|
||||
for (Employee employee : employees) {
|
||||
TreeItem<String> empLeaf = new TreeItem<String>(employee.getName());
|
||||
boolean found = false;
|
||||
for (TreeItem<String> depNode : rootNode.getChildren()) {
|
||||
if (depNode.getValue().contentEquals(employee.getDepartment())){
|
||||
depNode.getChildren().add(empLeaf);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
TreeItem<String> depNode = new TreeItem<String>(employee.getDepartment());
|
||||
rootNode.getChildren().add(depNode);
|
||||
depNode.getChildren().add(empLeaf);
|
||||
}
|
||||
}
|
||||
|
||||
stage.setTitle("Tree View Sample");
|
||||
VBox box = new VBox();
|
||||
final Scene scene = new Scene(box, 400, 300);
|
||||
scene.setFill(Color.LIGHTGRAY);
|
||||
|
||||
TreeView<String> treeView = new TreeView<String>(rootNode);
|
||||
treeView.setEditable(true);
|
||||
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
|
||||
@Override
|
||||
public TreeCell<String> call(TreeView<String> p) {
|
||||
return new TextFieldTreeCellImpl();
|
||||
}
|
||||
});
|
||||
|
||||
box.getChildren().add(treeView);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
private final class TextFieldTreeCellImpl extends TreeCell<String> {
|
||||
|
||||
private TextField textField;
|
||||
|
||||
public TextFieldTreeCellImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
super.startEdit();
|
||||
|
||||
if (textField == null) {
|
||||
createTextField();
|
||||
}
|
||||
setText(null);
|
||||
setGraphic(textField);
|
||||
textField.selectAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
setText((String) getItem());
|
||||
setGraphic(getTreeItem().getGraphic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
if (isEditing()) {
|
||||
if (textField != null) {
|
||||
textField.setText(getString());
|
||||
}
|
||||
setText(null);
|
||||
setGraphic(textField);
|
||||
} else {
|
||||
setText(getString());
|
||||
setGraphic(getTreeItem().getGraphic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createTextField() {
|
||||
textField = new TextField(getString());
|
||||
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
|
||||
|
||||
@Override
|
||||
public void handle(KeyEvent t) {
|
||||
if (t.getCode() == KeyCode.ENTER) {
|
||||
commitEdit(textField.getText());
|
||||
} else if (t.getCode() == KeyCode.ESCAPE) {
|
||||
cancelEdit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
return getItem() == null ? "" : getItem().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Employee {
|
||||
|
||||
private final SimpleStringProperty name;
|
||||
private final SimpleStringProperty department;
|
||||
|
||||
private Employee(String name, String department) {
|
||||
this.name = new SimpleStringProperty(name);
|
||||
this.department = new SimpleStringProperty(department);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public void setName(String fName) {
|
||||
name.set(fName);
|
||||
}
|
||||
|
||||
public String getDepartment() {
|
||||
return department.get();
|
||||
}
|
||||
|
||||
public void setDepartment(String fName) {
|
||||
department.set(fName);
|
||||
}
|
||||
}
|
||||
}
|
||||