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 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 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>() { @Override public void changed(ObservableValue> item, TreeItem oldValue, TreeItem 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 root = new TreeItem(new FileTreeDirectory("/")); root.setExpanded(true); fileTreeView.setRoot(root); } private void setupPropertySheet(){ //handle custom ProperySheet.Item's editor propertySheet.setPropertyEditorFactory(new Callback>(){ @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 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 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 tmpChild = new TreeItem(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 tmpChild = new TreeItem(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."); } } } } } }