diff --git a/README.md b/README.md index 3344954..2f06808 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ even in a "normal" CSV editor. So I decided to write this simple JavaFX applicat [Wiki & Documentation](https://github.com/frosch95/SmartCSV.fx/wiki) -binary distribution of the [latest release (0.4)](https://drive.google.com/open?id=0BwY9gBUvn5qmREdCc0FvNDNEQTA) +binary distribution of the [latest release (0.5)](https://drive.google.com/file/d/0BwY9gBUvn5qmejllOTRwbEJYdDA/view?usp=sharing) ##Talks [Introduction](http://javafx.ninja/talks/introduction/) diff --git a/build.gradle b/build.gradle index a8d8430..82b6981 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'ninja.javafx' -version '0.4-SNAPSHOT' +version '0.5-SNAPSHOT' apply plugin: 'java' apply plugin: 'groovy' diff --git a/settings.gradle b/settings.gradle index 0d0cf9a..07b97f9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -rootProject.name = 'SmartCSV.ninja.javafx.smartcsv.fx' +rootProject.name = 'SmartCSV.fx' diff --git a/src/main/java/ninja/javafx/smartcsv/fx/SmartCSVController.java b/src/main/java/ninja/javafx/smartcsv/fx/SmartCSVController.java index 773ac5e..beef4e2 100644 --- a/src/main/java/ninja/javafx/smartcsv/fx/SmartCSVController.java +++ b/src/main/java/ninja/javafx/smartcsv/fx/SmartCSVController.java @@ -26,6 +26,10 @@ package ninja.javafx.smartcsv.fx; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.WeakListChangeListener; import javafx.concurrent.WorkerStateEvent; @@ -37,11 +41,13 @@ import javafx.scene.control.*; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; import javafx.stage.FileChooser; +import javafx.util.converter.NumberStringConverter; import ninja.javafx.smartcsv.csv.CSVFileReader; import ninja.javafx.smartcsv.csv.CSVFileWriter; import ninja.javafx.smartcsv.files.FileStorage; import ninja.javafx.smartcsv.fx.about.AboutController; import ninja.javafx.smartcsv.fx.list.ErrorSideBar; +import ninja.javafx.smartcsv.fx.list.GotoLineDialog; import ninja.javafx.smartcsv.fx.preferences.PreferencesController; import ninja.javafx.smartcsv.fx.table.ObservableMapValueFactory; import ninja.javafx.smartcsv.fx.table.ValidationCellFactory; @@ -65,9 +71,11 @@ import org.supercsv.prefs.CsvPreference; import java.io.File; import java.io.IOException; import java.net.URL; +import java.text.MessageFormat; import java.util.Optional; import java.util.ResourceBundle; +import static java.lang.Integer.parseInt; import static java.lang.Math.max; import static java.text.MessageFormat.format; import static javafx.application.Platform.exit; @@ -153,6 +161,9 @@ public class SmartCSVController extends FXMLController { @FXML private MenuItem addRowMenuItem; + @FXML + private MenuItem gotoLineMenuItem; + @FXML private Button saveButton; @@ -177,6 +188,9 @@ public class SmartCSVController extends FXMLController { @FXML private Button addRowButton; + @FXML + private Label currentLineNumber; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // members //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -205,7 +219,7 @@ public class SmartCSVController extends FXMLController { setupTableCellFactory(); setupErrorSideBar(resourceBundle); - bindMenuItemsToContentExistence(currentCsvFile, saveMenuItem, saveAsMenuItem, addRowMenuItem, createConfigMenuItem, loadConfigMenuItem); + bindMenuItemsToContentExistence(currentCsvFile, saveMenuItem, saveAsMenuItem, addRowMenuItem, gotoLineMenuItem, createConfigMenuItem, loadConfigMenuItem); bindButtonsToContentExistence(currentCsvFile, saveButton, saveAsButton, addRowButton, createConfigButton, loadConfigButton); bindMenuItemsToContentExistence(currentConfigFile, saveConfigMenuItem, saveAsConfigMenuItem); @@ -346,6 +360,23 @@ public class SmartCSVController extends FXMLController { selectNewRow(); } + @FXML + public void gotoLine(ActionEvent actionEvent) { + int maxLineNumber = currentCsvFile.getContent().getRows().size(); + GotoLineDialog dialog = new GotoLineDialog(maxLineNumber); + dialog.setTitle(resourceBundle.getString("dialog.goto.line.title")); + dialog.setHeaderText(format(resourceBundle.getString("dialog.goto.line.header.text"), maxLineNumber)); + dialog.setContentText(resourceBundle.getString("dialog.goto.line.label")); + Optional result = dialog.showAndWait(); + if (result.isPresent()){ + Integer lineNumber = result.get(); + if (lineNumber != null) { + tableView.scrollTo(max(0, lineNumber - 2)); + tableView.getSelectionModel().select(lineNumber - 1); + } + } + } + public boolean canExit() { boolean canExit = true; if (currentCsvFile.getContent() != null && currentCsvFile.isFileChanged()) { @@ -425,6 +456,10 @@ public class SmartCSVController extends FXMLController { configurationName.textProperty().bind(selectString(currentConfigFile.fileProperty(), "name")); } + private void bindLineNumber() { + currentLineNumber.textProperty().bind(tableView.getSelectionModel().selectedIndexProperty().add(1).asString()); + } + private void loadCsvPreferencesFromFile() { if (csvPreferenceFile.getFile().exists()) { useLoadFileService(csvPreferenceFile, event -> setCsvPreference(csvPreferenceFile.getContent())); @@ -529,6 +564,7 @@ public class SmartCSVController extends FXMLController { currentCsvFile.getContent().setValidationConfiguration(currentConfigFile.getContent()); validationEditorController.setValidationConfiguration(currentConfigFile.getContent()); tableView = new TableView<>(); + bindLineNumber(); bindMenuItemsToTableSelection(deleteRowMenuItem); bindButtonsToTableSelection(deleteRowButton); diff --git a/src/main/java/ninja/javafx/smartcsv/fx/list/GotoLineDialog.java b/src/main/java/ninja/javafx/smartcsv/fx/list/GotoLineDialog.java new file mode 100644 index 0000000..f187ff6 --- /dev/null +++ b/src/main/java/ninja/javafx/smartcsv/fx/list/GotoLineDialog.java @@ -0,0 +1,91 @@ +package ninja.javafx.smartcsv.fx.list; + +import com.sun.javafx.scene.control.skin.resources.ControlResources; +import javafx.application.Platform; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; + +import java.util.function.UnaryOperator; + +/** + * Created by abi on 05.08.2016. + */ +public class GotoLineDialog extends Dialog { + + private final GridPane grid; + private final Label label; + private final TextField textField; + + public GotoLineDialog(int maxLineNumber) { + final DialogPane dialogPane = getDialogPane(); + + this.textField = new TextField(""); + this.textField.setMaxWidth(Double.MAX_VALUE); + + UnaryOperator filter = change -> { + String text = change.getText(); + + if (text.matches("[0-9]*")) { + try { + int lineNumber = Integer.parseInt(textField.getText() + text); + if (lineNumber <= maxLineNumber) { + return change; + } + } catch (NumberFormatException e) { + // this happens when focusing textfield or press special keys like DEL + return change; + } + } + + return null; + }; + TextFormatter textFormatter = new TextFormatter<>(filter); + textField.setTextFormatter(textFormatter); + + GridPane.setHgrow(textField, Priority.ALWAYS); + GridPane.setFillWidth(textField, true); + + label = createContentLabel(dialogPane.getContentText()); + label.setPrefWidth(Region.USE_COMPUTED_SIZE); + label.textProperty().bind(dialogPane.contentTextProperty()); + + this.grid = new GridPane(); + this.grid.setHgap(10); + this.grid.setMaxWidth(Double.MAX_VALUE); + this.grid.setAlignment(Pos.CENTER_LEFT); + + dialogPane.contentTextProperty().addListener(o -> updateGrid()); + + dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + updateGrid(); + + setResultConverter((dialogButton) -> { + ButtonBar.ButtonData data = dialogButton == null ? null : dialogButton.getButtonData(); + return data == ButtonBar.ButtonData.OK_DONE ? Integer.parseInt(textField.getText()) : null; + }); + } + + private Label createContentLabel(String text) { + Label label = new Label(text); + label.setMaxWidth(Double.MAX_VALUE); + label.setMaxHeight(Double.MAX_VALUE); + label.getStyleClass().add("content"); + label.setWrapText(true); + label.setPrefWidth(360); + return label; + } + + private void updateGrid() { + grid.getChildren().clear(); + + grid.add(label, 0, 0); + grid.add(textField, 1, 0); + getDialogPane().setContent(grid); + + Platform.runLater(() -> textField.requestFocus()); + } +} diff --git a/src/main/java/ninja/javafx/smartcsv/validation/ValidationConfiguration.java b/src/main/java/ninja/javafx/smartcsv/validation/ValidationConfiguration.java index ebb5ca2..a00da4b 100644 --- a/src/main/java/ninja/javafx/smartcsv/validation/ValidationConfiguration.java +++ b/src/main/java/ninja/javafx/smartcsv/validation/ValidationConfiguration.java @@ -52,6 +52,10 @@ public class ValidationConfiguration { headerConfiguration.setNames(headerNames); } + public Boolean getUniquenessRuleFor(String column) { + return (Boolean)getValue(column, "unique"); + } + public Boolean getIntegerRuleFor(String column) { return (Boolean)getValue(column, "integer"); } diff --git a/src/main/java/ninja/javafx/smartcsv/validation/Validator.java b/src/main/java/ninja/javafx/smartcsv/validation/Validator.java index 3e1cae4..b604da8 100644 --- a/src/main/java/ninja/javafx/smartcsv/validation/Validator.java +++ b/src/main/java/ninja/javafx/smartcsv/validation/Validator.java @@ -32,6 +32,7 @@ import groovy.lang.Script; import org.codehaus.groovy.control.CompilationFailedException; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -51,7 +52,7 @@ public class Validator { private ValidationConfiguration validationConfig; private GroovyShell shell = new GroovyShell(); private Map scriptCache = new HashMap<>(); - + private Map> uniquenessLookupTable = new HashMap<>(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // constructors @@ -92,6 +93,7 @@ public class Validator { checkGroovy(column, value, error); checkValueOf(column, value, error); checkDouble(column, value, error); + checkUniqueness(column, value, lineNumber, error); } if (!error.isEmpty()) { @@ -109,6 +111,29 @@ public class Validator { // private methods //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + private void checkUniqueness(String column, String value, Integer lineNumber, ValidationError error) { + if (validationConfig.getUniquenessRuleFor(column) != null && validationConfig.getUniquenessRuleFor(column)) { + HashMap columnValueMap = uniquenessLookupTable.get(column); + columnValueMap = getColumnValueMap(column, columnValueMap); + Integer valueInLineNumber = columnValueMap.get(value); + if (valueInLineNumber != null) { + if (!valueInLineNumber.equals(lineNumber)) { + error.add("validation.message.uniqueness", value, valueInLineNumber.toString()); + } + } else { + columnValueMap.put(value, lineNumber); + } + } + } + + private HashMap getColumnValueMap(String column, HashMap valueLineNumber) { + if (valueLineNumber == null) { + valueLineNumber = new HashMap<>(); + uniquenessLookupTable.put(column, valueLineNumber); + } + return valueLineNumber; + } + private void checkGroovy(String column, String value, ValidationError error) { String groovyScript = validationConfig.getGroovyRuleFor(column); if (groovyScript != null) { diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/application.properties b/src/main/resources/ninja/javafx/smartcsv/fx/application.properties index c37ae6c..0ca4f67 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/application.properties +++ b/src/main/resources/ninja/javafx/smartcsv/fx/application.properties @@ -1,5 +1,5 @@ application.name = SmartCSV.fx -application.version = 0.4 +application.version = 0.5 # fxml views fxml.smartcvs.view = /ninja/javafx/smartcsv/fx/smartcsv.fxml diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css index a3d6aa1..29850f3 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css +++ b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css @@ -79,6 +79,12 @@ -glyph-size: 16px; } +.goto-icon { + -glyph-name: "SUBDIRECTORY_ARROW_RIGHT"; + -glyph-size: 16px; + +} + /* toolbar customization based on http://fxexperience.com/2012/02/customized-segmented-toolbar-buttons/ */ #background { diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.fxml b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.fxml index 96d1739..2b66dd6 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.fxml +++ b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.fxml @@ -1,6 +1,7 @@ + @@ -21,8 +22,7 @@ - - + @@ -52,7 +52,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -114,6 +114,12 @@ + + + + + + @@ -164,7 +170,7 @@ - + @@ -177,7 +183,7 @@ - + @@ -190,7 +196,7 @@ - + @@ -203,7 +209,7 @@ - + @@ -275,7 +281,8 @@ - + + @@ -287,6 +294,8 @@