coder-client/src/com/coder/client/gui/editor/EditorWindow.java

370 lines
13 KiB
Java

package com.coder.client.gui.editor;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.controlsfx.control.PropertySheet;
import org.controlsfx.property.editor.PropertyEditor;
import zutil.log.LogUtil;
import com.coder.client.CoderClient;
import com.coder.client.ProjectHandler;
import com.coder.client.SessionHandler;
import com.coder.client.gui.GuiWindow;
import com.coder.client.project.OpenProjectEventHandler;
import com.coder.client.property.CoderClientProperty;
import com.coder.client.property.ComboBoxProperty;
import com.coder.client.session.FileRspMsgListener;
import com.coder.client.session.ProjectRspMsgListener;
import com.coder.client.session.ProjectTypeRspMsgListener;
import com.coder.server.message.CoderMessage;
import com.coder.server.message.FileReqMsg;
import com.coder.server.message.FileRspMsg;
import com.coder.server.message.FileSaveReqMsg;
import com.coder.server.message.ProjectReqMsg;
import com.coder.server.message.ProjectRspMsg;
import com.coder.server.message.ProjectTypeReqMsg;
import com.coder.server.message.ProjectTypeRspMsg;
import com.coder.server.message.SupportedProperties;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.util.Callback;
public class EditorWindow extends GuiWindow {
public static final Logger logger = LogUtil.getLogger();
private CoderClient client;
private SessionHandler sessionHandler;
private ProjectHandler projectHandler;
@FXML private TreeView<FileTreeItem> fileTreeView;
@FXML private TextArea editTextArea;
@FXML private PropertySheet propertySheet;
@FXML private Button compileButton;
@FXML private Button runButton;
@FXML private Button exitButton;
@FXML private Button changeProjectButton;
private String projectType = null;
public EditorWindow(final CoderClient client) throws IOException{
super(EditorWindow.class.getResource("EditorWindow.fxml"));
this.client = client;
this.sessionHandler = SessionHandler.getInstance();
this.projectHandler = ProjectHandler.getInstance();
sessionHandler.addMessageListener(new ProjectRspMsgListener() {
@Override
public void messageReceived(final ProjectRspMsg msg) {
try{
logger.fine("a ProjectRspMsg received");
if(msg.error != null){
logger.severe("Server responded on the project request with the following error message: " + msg.error);
projectHandler.setProject(null);
return;
}else{
projectHandler.setProject(msg.name);
}
//load project to GUI
loadProject(msg.name, msg.type, msg.description, msg.config, msg.fileList);
}catch(Exception e){
logger.log(Level.SEVERE, "exception while load the project", e);
projectHandler.triggerOpenProjectFailureEvent("ERROR: failed to loading project");
}
client.showOnStage(EditorWindow.this);
}
});
sessionHandler.addMessageListener(new ProjectTypeRspMsgListener() {
@Override
public void messageReceived(ProjectTypeRspMsg msg) {
logger.fine("handling a ProjectTypeRspMsg");
for(String type : msg.keySet()){ //for all project types in the ProjectTypeRspMsg
if(type.equals(projectType)){ //the current project type matches the project type in the message
logger.finer("the ProjectTypeRspMsg contins information for the curernt projects type ("+projectType+")");
SupportedProperties projectTypeSupportedProperties = msg.get(type); //get list of supported properties
for(String propertyName : projectTypeSupportedProperties.keySet()){ //for each property name
logger.finer("the project type has a property named \""+propertyName+"\"");
List<String> propertyAlternativeValues = projectTypeSupportedProperties.get(propertyName); //get the list of all valid alternative values to the property
logger.finer("looking for an existing property in the property sheet with the same name");
boolean foundProperty = false;
for(PropertySheet.Item propertySheetItem : propertySheet.getItems()){ //for all property sheet items in the GUI
if(propertySheetItem instanceof ComboBoxProperty){ //if the item is a combo box
ComboBoxProperty comboBoxProperty = (ComboBoxProperty) propertySheetItem; //cast to combo box
if(comboBoxProperty.getName().equals(propertyName)){ //if combo box property name is the same as the property name
logger.finer("the property sheet already contained a property with the same name.");
foundProperty = true;
logger.finer("adding alternative values to the existing property");
comboBoxProperty.setAlternativeValues(propertyAlternativeValues); //add the alternative values
}
}
}
if(!foundProperty){
logger.finer("the property was not present in the property sheet. adding it.");
ComboBoxProperty comboProperty = new ComboBoxProperty(propertyName, null, propertyAlternativeValues);
propertySheet.getItems().add(comboProperty);
}
}
}
}
}
});
sessionHandler.addMessageListener(new FileRspMsgListener(){
@Override
public void messageReceived(FileRspMsg msg) {
if(msg.error != null || msg.path == null){
if(msg.error != null){
logger.severe("recieved error message \""+msg.error+"\" the FileRspMsg");
}
if(msg.path == null){
logger.severe("recieved a file with a null path");
setErrorMessage("ERROR: en error occured while loading a file");
}else{
setErrorMessage("ERROR: en error occured while loading the file: \""+msg.path+"\"");
}
return;
}else{
setErrorMessage("");
}
logger.fine("recieved file content for file: \""+msg.path+"\"");
String data = new String(msg.data, StandardCharsets.UTF_8);
logger.fine("loading file content to edit text area");
editTextArea.setText(data);
editTextArea.setDisable(false);
}
});
projectHandler.addprojectEventHandler(new OpenProjectEventHandler() {
@Override
public void openProject(String projectName) {
CoderMessage msg = new CoderMessage();
msg.ProjectReq = new ProjectReqMsg();
msg.ProjectReq.name = projectName;
sessionHandler.sendMessage(msg);
}
});
}
@Override
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
setErrorMessage("");
setupPropertySheet();
setupFileTreeView();
setupEditTextArea();
}
@Override
public void willShow(){
editTextArea.setText("");
editTextArea.setDisable(true);
}
@FXML
protected void run(ActionEvent event){
//TODO
}
@FXML
protected void compile(ActionEvent event){
//TODO
}
@FXML
protected void changeProject(ActionEvent event){
projectHandler.setProject(null);
projectHandler.triggerSelectProjectEvent();
}
@FXML
protected void exit(ActionEvent event){
client.exit();
}
private void setupFileTreeView(){
fileTreeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<FileTreeItem>>() {
@Override
public void changed(ObservableValue<? extends TreeItem<FileTreeItem>> item, TreeItem<FileTreeItem> oldValue, TreeItem<FileTreeItem> newValue) {
if(newValue != fileTreeView.getRoot()){
FileTreeItem fileTreeItem = newValue.getValue();
if(fileTreeItem.isDirectory()){
FileTreeDirectory directory = (FileTreeDirectory)fileTreeItem;
logger.fine("directory " + directory.getName() + " selected in the file tree");
System.out.println();
}else{
FileTreeFile file = (FileTreeFile)fileTreeItem;
logger.fine("file " + file.getName() + " selected in the file tree. The file has the full path: \""+file.getFullPath()+"\"");
editTextArea.setDisable(true);
//sending file request message
CoderMessage msg = new CoderMessage();
msg.FileReq = new FileReqMsg();
msg.FileReq.path = file.getFullPath();
sessionHandler.sendMessage(msg);
}
}
}
});
TreeItem<FileTreeItem> root = new TreeItem<FileTreeItem>(new FileTreeDirectory("/"));
root.setExpanded(true);
fileTreeView.setRoot(root);
}
private void setupPropertySheet(){
//handle custom ProperySheet.Item's editor
propertySheet.setPropertyEditorFactory(new Callback<PropertySheet.Item, PropertyEditor<?>>(){
@Override
public PropertyEditor<?> call(PropertySheet.Item param) {
//only support internal types
if(param instanceof CoderClientProperty){
return ((CoderClientProperty<?>)param).getEditor();
}
//not a internal property type
return null;
}
});
propertySheet.getItems().clear();
}
private void setupEditTextArea(){
editTextArea.focusedProperty().addListener((observable, oldValue, newValue)->{
if(!editTextArea.isFocused()){
logger.fine("edit text area lost focus");
logger.fine("will send a file save req");
CoderMessage msg = new CoderMessage();
msg.FileSaveReq = new FileSaveReqMsg();
msg.FileSaveReq.path = ((FileTreeFile)fileTreeView.getSelectionModel().getSelectedItem().getValue()).getFullPath();
msg.FileSaveReq.data = editTextArea.getText().getBytes(StandardCharsets.UTF_8);
sessionHandler.sendMessage(msg);
}
});
}
@Override
public String getTitle() {
return "Coder Client";
}
private void setErrorMessage(String msg) {
// TODO Auto-generated method stub
}
@Override
public String getDescriptiveName() {
return "Editor Window";
}
private void loadProject(final String projectName, final String projectType, final String projectDescription, final Properties projectConfig, ArrayList<String> projectFileList){
logger.info("loading project \""+projectName+"\"");
//TODO: show "loading project" popup
//handle name and description
fileTreeView.getRoot().setValue(new FileTreeDirectory(projectName)); //set file tree root name to the project name
//handle config
propertySheet.getItems().clear();
if(projectConfig != null){ // projectConfig is an optional parameter
logger.fine("the project has a configuration - populating property sheet");
Enumeration<?> propertyNames = projectConfig.propertyNames();
while(propertyNames.hasMoreElements()){ //populate propertySheet with all config elements
Object propertyNameObject = propertyNames.nextElement();
if(propertyNameObject instanceof String){
String propertyName = (String)propertyNameObject;
String propertyValue = projectConfig.getProperty(projectName);
ComboBoxProperty comboProperty = new ComboBoxProperty(propertyName, propertyValue, null);
propertySheet.getItems().add(comboProperty);
}
}
//request alternative values for config of this project type
this.projectType = projectType;
//send ProjectTypeReqMsg for the project type
CoderMessage msg = new CoderMessage();
msg.ProjectTypeReq = new ProjectTypeReqMsg();
msg.ProjectTypeReq.type = projectType;
sessionHandler.sendMessage(msg);
}else{
logger.fine("the project has no configuration");
}
//handle file list
fileTreeView.getRoot().getChildren().clear();
for(String filePath : projectFileList){
String fullPath = filePath;
logger.finer("adding file \""+filePath+"\" to the file tree");
if(filePath.endsWith("/")){
logger.warning("file: \"" + filePath + "\" in file list is a directory and not a file. Currently not supported. Ignoring entry");
continue;
}
if(filePath.startsWith("/")){
filePath = filePath.substring(1, filePath.length());
}
TreeItem<FileTreeItem> tmpParent = fileTreeView.getRoot();
String[] filePathSpilt = filePath.split("/");
for(int i = 0; i < filePathSpilt.length; ++i){
if(i < filePathSpilt.length-1){
String directoryName = filePathSpilt[i];
if(!tmpParent.getChildren().contains(directoryName)){
if(tmpParent == fileTreeView.getRoot()){
logger.finer("adding directory \""+directoryName+"\" to directory \"/\"");
}else{
logger.finer("adding directory \""+directoryName+"\" to directory \""+tmpParent+"\"");
}
TreeItem<FileTreeItem> tmpChild = new TreeItem<FileTreeItem>(new FileTreeDirectory(directoryName));
tmpParent.getChildren().add(tmpChild);
tmpParent = tmpChild;
}else{
int index = tmpParent.getChildren().indexOf(directoryName);
tmpParent = tmpParent.getChildren().get(index);
}
}else{
String fileName = filePathSpilt[i];
if(!tmpParent.getChildren().contains(fileName)){
if(tmpParent == fileTreeView.getRoot()){
logger.finer("adding file \""+fileName+"\" to directory \"/\"");
}else{
logger.finer("adding file \""+fileName+"\" to directory \""+tmpParent+"\"");
}
TreeItem<FileTreeItem> tmpChild = new TreeItem<FileTreeItem>(new FileTreeFile(fileName, fullPath));
tmpParent.getChildren().add(tmpChild);
tmpParent = tmpChild;
}else{
logger.warning("The project seems to contain two or more files with the same location and name. The file tree presented may be missing files. duplicates not allowed.");
}
}
}
}
}
}