/*
* Copyright (C) 2015 Adrien Guille <adrien.guille@univ-lyon2.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main.java.fr.ericlab.sondy.core.ui;
import com.sun.javafx.css.StyleManager;
import impl.org.controlsfx.skin.CheckComboBoxSkin;
import main.java.fr.ericlab.sondy.core.app.Main;
import main.java.fr.ericlab.sondy.core.app.AppParameters;
import main.java.fr.ericlab.sondy.core.structures.Dataset;
import main.java.fr.ericlab.sondy.core.structures.Datasets;
import main.java.fr.ericlab.sondy.core.text.stopwords.StopwordSets;
import main.java.fr.ericlab.sondy.core.utils.UIUtils;
import java.io.File;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Separator;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import org.controlsfx.control.CheckComboBox;
import org.controlsfx.control.PropertySheet;
import org.controlsfx.control.RangeSlider;
/**
*
* @author Adrien GUILLE, Laboratoire ERIC, Université Lumière Lyon 2
*/
public class DataManipulationUI {
public GridPane grid;
// Available datasets
// - list
ListView<String> datasetListView;
Datasets availableDatasets;
// Selected dataset
// - info
Label selectedDatasetIdLabel;
Label selectedDatasetDescriptionLabel;
Label messageCountLabel;
Label userCountLabel;
Label relationshipCountLabel;
// - available preprocessed corpora
ChoiceBox preprocessedCorpusList;
// Import
TextField newDatasetIdentifierField;
TextField newDatasetDescriptionField;
// - property map
Button messagesFileButton;
Button networkFileButton;
HashMap<String,String> newDatasetProperties;
// - Preprocess
ChoiceBox stemmingChoiceBox;
ChoiceBox lemmatizationChoiceBox;
ChoiceBox tokenizationChoiceBox;
TextField timeSliceLengthField;
// - Filter
CheckComboBox<String> stopwordListsCheckComboBox;
StopwordSets stopwordLists;
RangeSlider resizeSlider;
public DataManipulationUI(){
StyleManager.getInstance().addUserAgentStylesheet(PropertySheet.class.getResource("propertysheet.css").toExternalForm());
// Initializing the main grid
grid = new GridPane();
grid.setPadding(new Insets(5,5,5,5));
// Adding separators
grid.add(new Text("Available datasets"),0,0);
grid.add(new Separator(),0,1);
grid.add(new Text("Import a dataset"),0,3);
grid.add(new Separator(),0,4);
grid.add(new Text("Preprocess the selected dataset"),0,6);
grid.add(new Separator(),0,7);
grid.add(new Text("Filter the selected preprocessed dataset"),0,9);
grid.add(new Separator(),0,10);
// Initializing specific UIs
availableDatasetsUI();
newDatasetProperties = new HashMap<>();
importUI();
preprocessUI();
filterUI();
}
public final void availableDatasetsUI(){
initializeDatasetListView();
selectedDatasetDescriptionLabel = new Label("Selected dataset description");
selectedDatasetDescriptionLabel.setId("smalltext");
UIUtils.setSize(selectedDatasetDescriptionLabel,Main.columnWidthLEFT,24);
VBox existingDatasetLEFT = new VBox();
existingDatasetLEFT.getChildren().addAll(datasetListView,new Rectangle(0,3),selectedDatasetDescriptionLabel);
// Right part
selectedDatasetIdLabel = new Label("Selected dataset id");
selectedDatasetIdLabel.setId("smalltext");
UIUtils.setSize(selectedDatasetIdLabel,Main.columnWidthRIGHT,16);
messageCountLabel = new Label("Message count");
messageCountLabel.setId("smalltext");
UIUtils.setSize(messageCountLabel,Main.columnWidthRIGHT,16);
userCountLabel = new Label("User count");
userCountLabel.setId("smalltext");
UIUtils.setSize(userCountLabel,Main.columnWidthRIGHT,16);
relationshipCountLabel = new Label("Relationship count");
relationshipCountLabel.setId("smalltext");
UIUtils.setSize(relationshipCountLabel,Main.columnWidthRIGHT,16);
initializePreprocessedCorpusList();
UIUtils.setSize(preprocessedCorpusList, Main.columnWidthRIGHT, 24);
preprocessedCorpusList.setItems(AppParameters.dataset.preprocessedCorpusList);
VBox existingDatasetRIGHT = new VBox();
existingDatasetRIGHT.getChildren().addAll(selectedDatasetIdLabel,messageCountLabel,userCountLabel,relationshipCountLabel,new Rectangle(0,3),preprocessedCorpusList);
// Both parts
HBox existingDatasetBOTH = new HBox(5);
existingDatasetBOTH.getChildren().addAll(existingDatasetLEFT,existingDatasetRIGHT);
grid.add(existingDatasetBOTH,0,2);
}
public final void importUI(){
GridPane gridLEFT = new GridPane();
// Labels
Label messagesLabel = new Label("Messages file");
UIUtils.setSize(messagesLabel, Main.columnWidthLEFT/2, 24);
Label networkLabel = new Label("Network file");
UIUtils.setSize(networkLabel, Main.columnWidthLEFT/2, 24);
Label datasetIdentificatorLabel = new Label("Dataset ID");
UIUtils.setSize(datasetIdentificatorLabel, Main.columnWidthLEFT/2, 24);
Label datasetDescriptionLabel = new Label("Dataset description");
UIUtils.setSize(datasetDescriptionLabel, Main.columnWidthLEFT/2, 24);
gridLEFT.add(messagesLabel,0,0);
gridLEFT.add(new Rectangle(0,3),0,1);
gridLEFT.add(networkLabel,0,2);
gridLEFT.add(new Rectangle(0,3),0,3);
gridLEFT.add(datasetIdentificatorLabel,0,4);
gridLEFT.add(new Rectangle(0,3),0,5);
gridLEFT.add(datasetDescriptionLabel,0,6);
// Values
messagesFileButton = createChooseFileButton("messagesFile");
UIUtils.setSize(messagesFileButton,Main.columnWidthLEFT/2, 24);
networkFileButton = createChooseFileButton("networkFile");
UIUtils.setSize(networkFileButton,Main.columnWidthLEFT/2, 24);
newDatasetIdentifierField = new TextField();
newDatasetIdentifierField.setPromptText("unique identifier");
UIUtils.setSize(newDatasetIdentifierField,Main.columnWidthLEFT/2, 24);
newDatasetDescriptionField = new TextField();
newDatasetDescriptionField.setPromptText("short dataset description");
UIUtils.setSize(newDatasetDescriptionField,Main.columnWidthLEFT/2, 24);
gridLEFT.add(messagesFileButton,1,0);
gridLEFT.add(networkFileButton,1,2);
gridLEFT.add(newDatasetIdentifierField,1,4);
gridLEFT.add(newDatasetDescriptionField,1,6);
HBox importDatasetBOTH = new HBox(5);
importDatasetBOTH.getChildren().addAll(gridLEFT,createImportButton());
grid.add(importDatasetBOTH,0,5);
}
public final void preprocessUI(){
GridPane gridLEFT = new GridPane();
// Labels
Label stemmingLabel = new Label("Stemming");
UIUtils.setSize(stemmingLabel, Main.columnWidthLEFT/2, 24);
Label LemmatizationLabel = new Label("Lemmatization");
UIUtils.setSize(LemmatizationLabel, Main.columnWidthLEFT/2, 24);
Label TokenizationLabel = new Label("Tokenization");
UIUtils.setSize(TokenizationLabel, Main.columnWidthLEFT/2, 24);
Label partitionLabel = new Label("Partition and index messages");
UIUtils.setSize(partitionLabel, Main.columnWidthLEFT/2, 24);
gridLEFT.add(stemmingLabel,0,0);
gridLEFT.add(new Rectangle(0,3),0,1);
gridLEFT.add(LemmatizationLabel,0,2);
gridLEFT.add(new Rectangle(0,3),0,3);
gridLEFT.add(TokenizationLabel,0,4);
gridLEFT.add(new Rectangle(0,3),0,5);
gridLEFT.add(partitionLabel,0,6);
// Values
stemmingChoiceBox = new ChoiceBox();
stemmingChoiceBox.getItems().addAll("disabled","French","English","Arabic","Persian");
UIUtils.setSize(stemmingChoiceBox,Main.columnWidthLEFT/2, 24);
lemmatizationChoiceBox = new ChoiceBox();
lemmatizationChoiceBox.getItems().addAll("disabled","French","English");
UIUtils.setSize(lemmatizationChoiceBox,Main.columnWidthLEFT/2, 24);
tokenizationChoiceBox = new ChoiceBox();
tokenizationChoiceBox.getItems().addAll("1-gram","2-gram","3-gram");
UIUtils.setSize(tokenizationChoiceBox,Main.columnWidthLEFT/2, 24);
timeSliceLengthField = new TextField();
timeSliceLengthField.setPromptText("time-slice length in minutes (e.g. 30)");
UIUtils.setSize(timeSliceLengthField,Main.columnWidthLEFT/2, 24);
gridLEFT.add(stemmingChoiceBox,1,0);
gridLEFT.add(lemmatizationChoiceBox,1,2);
gridLEFT.add(tokenizationChoiceBox,1,4);
gridLEFT.add(timeSliceLengthField,1,6);
HBox preprocessDatasetBOTH = new HBox(5);
preprocessDatasetBOTH.getChildren().addAll(gridLEFT,createPreprocessButton());
grid.add(preprocessDatasetBOTH,0,8);
}
public final void filterUI(){
GridPane gridLEFT = new GridPane();
// Labels
Label stopwordsLabel = new Label("Stop words removal");
UIUtils.setSize(stopwordsLabel, Main.columnWidthLEFT/2, 24);
Label resizingLabel = new Label("Resizing");
UIUtils.setSize(resizingLabel, Main.columnWidthLEFT/2, 24);
gridLEFT.add(stopwordsLabel,0,0);
gridLEFT.add(new Rectangle(0,3),0,1);
gridLEFT.add(resizingLabel,0,2);
// Values
stopwordLists = new StopwordSets();
stopwordListsCheckComboBox = new CheckComboBox<>(stopwordLists.availableSets);
stopwordListsCheckComboBox.setStyle("-fx-font-size: 12px;");
stopwordListsCheckComboBox.skinProperty().addListener(new ChangeListener<Skin>() {
@Override
public void changed(ObservableValue<? extends Skin> observable, Skin oldValue, Skin newValue) {
if(oldValue==null && newValue!=null){
CheckComboBoxSkin skin = (CheckComboBoxSkin)newValue;
ComboBox combo = (ComboBox)skin.getChildren().get(0);
combo.setPrefWidth(Main.columnWidthLEFT/2);
combo.setMaxWidth(Double.MAX_VALUE);
}
}
});
// stopwordListsCheckComboBox.setMaxWidth(Double.MAX_VALUE);
// UIUtils.setSize(stopwordListsCheckComboBox,Main.columnWidthLEFT/2, 24);
gridLEFT.add(stopwordListsCheckComboBox,1,0);
resizeSlider = new RangeSlider();
resizeSlider.setBlockIncrement(0.1);
UIUtils.setSize(resizeSlider,Main.columnWidthLEFT/2, 24);
resizeSlider.resize(Main.columnWidthLEFT/2, 24);
gridLEFT.add(resizeSlider,1,2);
HBox filterDatasetBOTH = new HBox(5);
filterDatasetBOTH.getChildren().addAll(gridLEFT,createFilterButton());
grid.add(filterDatasetBOTH,0,11);
}
public final void initializeDatasetListView(){
availableDatasets = new Datasets();
availableDatasets.update();
datasetListView = new ListView<>();
datasetListView.setOrientation(Orientation.HORIZONTAL);
datasetListView.setItems(availableDatasets.list);
datasetListView.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends String> ov, String old_val, String new_val) -> {
if(new_val != null){
selectedDatasetIdLabel.setText(new_val);
AppParameters.dataset.load(new_val);
messageCountLabel.setText(AppParameters.dataset.corpus.messageCount+" messages");
userCountLabel.setText(AppParameters.dataset.corpus.authorCount+" users");
relationshipCountLabel.setText(AppParameters.dataset.network.relationshipCount+" relationships");
selectedDatasetDescriptionLabel.setText(AppParameters.dataset.description);
resizeSlider.setMin(0);
resizeSlider.setLowValue(0);
resizeSlider.setMax(AppParameters.dataset.corpus.getLength());
resizeSlider.setHighValue(AppParameters.dataset.corpus.getLength());
clearPreprocessUI();
}
});
UIUtils.setSize(datasetListView,Main.columnWidthLEFT,64);
}
public final void initializePreprocessedCorpusList(){
preprocessedCorpusList = new ChoiceBox();
UIUtils.setSize(preprocessedCorpusList, Main.columnWidthRIGHT, 24);
preprocessedCorpusList.setItems(AppParameters.dataset.preprocessedCorpusList);
preprocessedCorpusList.valueProperty().addListener(new ChangeListener<String>() {
@Override public void changed(ObservableValue ov, String t, String t1) {
clearFilterUI();
if(t1 != null){
LogUI.addLogEntry("Loading '"+AppParameters.dataset.id+"' ("+t1+")... ");
AppParameters.dataset.corpus.loadFrequencies(t1);
AppParameters.timeSliceA = 0;
AppParameters.timeSliceB = AppParameters.dataset.corpus.messageDistribution.length;
LogUI.addLogEntry("Done.");
resizeSlider.setMin(0);
resizeSlider.setLowValue(0);
resizeSlider.setMax(AppParameters.dataset.corpus.getLength());
resizeSlider.setHighValue(AppParameters.dataset.corpus.getLength());
}
}
});
}
public final Button createChooseStopwordsButton(){
Button button = new Button("Choose stopword sets...");
button.setOnAction((ActionEvent event) -> {
});
return button;
}
public final Button createChooseFileButton(String buttonId){
Button button = new Button("Choose file...");
button.setOnAction((ActionEvent event) -> {
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("CSV files (*.csv)", "*.csv");
fileChooser.getExtensionFilters().add(extFilter);
File file = fileChooser.showOpenDialog(null);
if(file != null){
newDatasetProperties.put(buttonId, file.getAbsolutePath());
button.setText(Paths.get(file.getPath()).getFileName().toString());
}
});
return button;
}
public final VBox createImportButton(){
VBox buttonBox = new VBox();
buttonBox.setAlignment(Pos.CENTER);
Button importButton = new Button("Import");
UIUtils.setSize(importButton,Main.columnWidthRIGHT,24);
importButton.setOnAction((ActionEvent event) -> {
AppParameters.disableUI(true);
newDatasetProperties.put("id",newDatasetIdentifierField.getText());
newDatasetProperties.put("description",newDatasetDescriptionField.getText());
Dataset dataset = new Dataset();
AppParameters.disableUI(true);
String messagesFilePath = newDatasetProperties.get("messagesFile");
String networkFilePath = newDatasetProperties.get("networkFile");
LogUI.addLogEntry("Importing '"+newDatasetIdentifierField.getText()+"' (messages file: "+messagesFilePath+", network file: "+networkFilePath+")...");
final Task<String> waitingTask = new Task<String>() {
@Override
public String call() throws Exception {
String[] log = dataset.create(newDatasetProperties);
LogUI.addLogEntry(log[0]);
LogUI.addLogEntry(log[1]);
return "" ;
}
};
waitingTask.setOnSucceeded((WorkerStateEvent event1) -> {
newDatasetProperties.clear();
availableDatasets.update();
messagesFileButton.setText("Choose file...");
networkFileButton.setText("Choose file...");
newDatasetIdentifierField.clear();
newDatasetDescriptionField.clear();
AppParameters.disableUI(false);
});
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(waitingTask);
});
buttonBox.getChildren().addAll(importButton);
return buttonBox;
}
public final VBox createPreprocessButton(){
VBox buttonBox = new VBox();
buttonBox.setAlignment(Pos.CENTER);
Button preprocessButton = new Button("Preprocess");
UIUtils.setSize(preprocessButton,Main.columnWidthRIGHT,24);
preprocessButton.setOnAction((ActionEvent event) -> {
AppParameters.disableUI(true);
int ngram = Integer.parseInt(tokenizationChoiceBox.getSelectionModel().getSelectedItem().toString().split("-")[0]);
int timeSliceLength = Integer.parseInt(timeSliceLengthField.getText());
String stemming = stemmingChoiceBox.getSelectionModel().getSelectedItem().toString();
String lemmatization = lemmatizationChoiceBox.getSelectionModel().getSelectedItem().toString();
LogUI.addLogEntry("Preprocessing '"+AppParameters.dataset.id+"' (stemming: "+stemming+", lemmatization: "+lemmatization+", "+ngram+"-gram, "+timeSliceLength+"-min time-slices)...");
final Task<String> waitingTask = new Task<String>() {
@Override
public String call() throws Exception {
String log = AppParameters.dataset.preprocess(stemming,lemmatization,ngram,timeSliceLength);
LogUI.addLogEntry(log);
return "";
}
};
waitingTask.setOnSucceeded((WorkerStateEvent event1) -> {
AppParameters.dataset.preprocess(stemmingChoiceBox.getSelectionModel().getSelectedItem().toString(),lemmatizationChoiceBox.getSelectionModel().getSelectedItem().toString(),ngram,timeSliceLength);
AppParameters.dataset.updatePreprocessedCorpusList();
AppParameters.disableUI(false);
clearPreprocessUI();
});
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(waitingTask);
});
buttonBox.getChildren().addAll(preprocessButton);
return buttonBox;
}
public final VBox createFilterButton(){
VBox buttonBox = new VBox();
buttonBox.setAlignment(Pos.CENTER);
Button filterButton = new Button("Filter");
UIUtils.setSize(filterButton,Main.columnWidthRIGHT,24);
filterButton.setOnAction((ActionEvent event) -> {
if(!AppParameters.dataset.corpus.preprocessing.equals("")){
LogUI.addLogEntry("Filtering '"+AppParameters.dataset.id+"' (stop word set(s): "+stopwordListsCheckComboBox.getCheckModel().getCheckedItems()+", time period: ["+resizeSlider.getLowValue()+";"+resizeSlider.getHighValue()+"])");
AppParameters.timeSliceA = AppParameters.dataset.corpus.convertDayToTimeSlice(resizeSlider.getLowValue());
if(resizeSlider.getHighValue() != AppParameters.dataset.corpus.getLength())
AppParameters.timeSliceB = AppParameters.dataset.corpus.convertDayToTimeSlice(resizeSlider.getHighValue());
AppParameters.updateStopwords(stopwordListsCheckComboBox.getCheckModel().getCheckedItems());
LogUI.addLogEntry("Done.");
}else{
LogUI.addLogEntry("Error: no dataset loaded");
}
});
buttonBox.getChildren().addAll(filterButton);
return buttonBox;
}
public final void clearPreprocessUI(){
stemmingChoiceBox.getSelectionModel().clearSelection();
lemmatizationChoiceBox.getSelectionModel().clearSelection();
tokenizationChoiceBox.getSelectionModel().clearSelection();
timeSliceLengthField.clear();
preprocessedCorpusList.getSelectionModel().clearSelection();
}
public final void clearFilterUI(){
stopwordListsCheckComboBox.getCheckModel().clearChecks();
resizeSlider.setHighValue(resizeSlider.getHighValue());
resizeSlider.setLowValue(resizeSlider.getLowValue());
}
}