pushed local repository online

This commit is contained in:
2025-09-08 18:33:55 +08:00
parent 77f6bc924c
commit 8b6668d88e
119 changed files with 18216 additions and 0 deletions

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

View 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);
}
}
}

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

View File

@ -0,0 +1,10 @@
package leylines.dialog;
public interface ReplicaActionHandler {
/**
* @return ID of the replica selected by custom script logic
*/
public int executeScriptLogic();
}

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

View File

@ -0,0 +1,10 @@
/*
Created on : Dec 4, 2024, 6:24:46PM
Author : Sova <@graymouse at equestria.social>
*/
.label {
-fx-font-style: italic;
-fx-font-size: 12;
}

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

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

View 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);
}
}

View File

@ -0,0 +1,67 @@
/*
Created on : Nov 3, 2024, 2:33:36AM
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;
}

View 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>

View 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);
}
}
}

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

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

View 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;
}
}
/* ========================================== */

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

View 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>

View File

@ -0,0 +1,19 @@
/*
Created on : Nov 21, 2024, 4:08:45PM
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
View 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 )

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

207
src/test/Controller.java Normal file
View 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));
}
}

View 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);
}
}

View 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");
}
}
}

View 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);
}
}
}