removed the error list and added a clickable sidaebar wih error markers to have more editing space

This commit is contained in:
Andreas Billmann
2016-01-27 04:39:53 +01:00
parent b4cc3f9922
commit 26c06a0908
6 changed files with 255 additions and 123 deletions

View File

@@ -43,7 +43,7 @@ import ninja.javafx.smartcsv.FileWriter;
import ninja.javafx.smartcsv.csv.CSVFileReader;
import ninja.javafx.smartcsv.csv.CSVFileWriter;
import ninja.javafx.smartcsv.fx.about.AboutController;
import ninja.javafx.smartcsv.fx.list.ValidationErrorListCell;
import ninja.javafx.smartcsv.fx.list.ErrorSideBar;
import ninja.javafx.smartcsv.fx.preferences.PreferencesController;
import ninja.javafx.smartcsv.fx.table.ObservableMapValueFactory;
import ninja.javafx.smartcsv.fx.table.ValidationCellFactory;
@@ -132,9 +132,6 @@ public class SmartCSVController extends FXMLController {
@FXML
private Label stateName;
@FXML
private ListView errorList;
@FXML
private AnchorPane tableWrapper;
@@ -162,6 +159,8 @@ public class SmartCSVController extends FXMLController {
@FXML
private Button addRowButton;
private ErrorSideBar errorSideBar;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// members
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -185,8 +184,13 @@ public class SmartCSVController extends FXMLController {
this.resourceBundle = resourceBundle;
setupTableCellFactory();
setupErrorListCellFactory();
setupErrorListSelectionListener();
errorSideBar = new ErrorSideBar(resourceBundle);
errorSideBar.selectedValidationErrorProperty().addListener((observable, oldValue, newValue) -> {
scrollToError(newValue);
});
applicationPane.setRight(errorSideBar);
bindMenuItemsToCsvFileExtistence(saveMenuItem, saveAsMenuItem, addRowMenuItem);
bindButtonsToCsvFileExistence(saveButton, saveAsButton, addRowButton);
@@ -196,14 +200,6 @@ public class SmartCSVController extends FXMLController {
loadCsvPreferencesFromFile();
}
private void setupErrorListSelectionListener() {
errorList.getSelectionModel().selectedItemProperty().addListener(observable -> scrollToError());
}
private void setupErrorListCellFactory() {
errorList.setCellFactory(param -> new ValidationErrorListCell(resourceBundle));
}
private void setupTableCellFactory() {
cellFactory = new ValidationCellFactory(resourceBundle);
}
@@ -510,8 +506,7 @@ public class SmartCSVController extends FXMLController {
setLeftAnchor(tableView, 0.0);
setRightAnchor(tableView, 0.0);
tableWrapper.getChildren().setAll(tableView);
errorList.setItems(model.getValidationError());
errorSideBar.setModel(model);
}
}
@@ -540,8 +535,7 @@ public class SmartCSVController extends FXMLController {
tableView.getColumns().add(column);
}
private void scrollToError() {
ValidationError entry = (ValidationError)errorList.getSelectionModel().getSelectedItem();
private void scrollToError(ValidationError entry) {
if (entry != null) {
if (entry.getLineNumber() != null) {
tableView.scrollTo(max(0, entry.getLineNumber() - 1));

View File

@@ -0,0 +1,201 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.list;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.validation.ValidationError;
import org.controlsfx.control.PopOver;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/**
* clickable side bar with error markers
*/
public class ErrorSideBar extends Pane {
private ListChangeListener<ValidationError> errorListListener = c -> requestLayout();
private WeakListChangeListener<ValidationError> weakErrorListListener = new WeakListChangeListener<>(errorListListener);
private ObjectProperty<CSVModel> model = new SimpleObjectProperty<>();
private Canvas canvas = new Canvas();
private ObjectProperty<ValidationError> selectedValidationError = new SimpleObjectProperty<>();
private PopOver popOver = new PopOver();
private static final double WIDTH = 20.0;
public ErrorSideBar(ResourceBundle resourceBundle) {
getChildren().add(canvas);
setFixWidth();
configurePopOver();
addModelListener();
addMouseClickListener();
addOnMouseOverListenerForPopOver(resourceBundle);
}
public void setModel(CSVModel model) {
this.model.set(model);
}
public CSVModel getModel() {
return model.get();
}
public ObjectProperty<CSVModel> modelProperty() {
return model;
}
public ValidationError getSelectedValidationError() {
return selectedValidationError.get();
}
public ObjectProperty<ValidationError> selectedValidationErrorProperty() {
return selectedValidationError;
}
public void setSelectedValidationError(ValidationError selectedValidationError) {
this.selectedValidationError.set(selectedValidationError);
}
private void addOnMouseOverListenerForPopOver(ResourceBundle resourceBundle) {
setOnMouseMoved(event -> {
List<ValidationError> errors = findValidationErrors(event.getY());
if (!errors.isEmpty()) {
StringWriter messages = new StringWriter();
for (ValidationError validationError: errors) {
messages.append(getI18nValidatioMessage(resourceBundle, validationError)).append("\n");
}
popOver.setContentNode(popupContent(messages.toString()));
popOver.show(ErrorSideBar.this.getParent(), event.getScreenX()-WIDTH, event.getScreenY());
} else {
popOver.hide();
}
});
}
private void addMouseClickListener() {
setOnMouseClicked(event -> {
List<ValidationError> errors = findValidationErrors(event.getY());
if (!errors.isEmpty()) {
selectedValidationError.setValue(errors.get(0));
}
});
}
private void addModelListener() {
model.addListener((observable, oldValue, newValue) -> {
newValue.getValidationError().addListener(weakErrorListListener);
requestLayout();
});
}
private void configurePopOver() {
popOver.setArrowLocation(PopOver.ArrowLocation.RIGHT_CENTER);
}
private void setFixWidth() {
setMinWidth(WIDTH);
setPrefWidth(WIDTH);
setMaxWidth(WIDTH);
}
@Override
protected void layoutChildren() {
int top = (int) snappedTopInset();
int right = (int) snappedRightInset();
int bottom = (int) snappedBottomInset();
int left = (int) snappedLeftInset();
int w = (int) getWidth() - left - right;
int h = (int) getHeight() - top - bottom;
canvas.setLayoutX(left);
canvas.setLayoutY(top);
if (w != canvas.getWidth() || h != canvas.getHeight()) {
canvas.setWidth(w);
canvas.setHeight(h);
}
drawErrorMarker(w, h);
}
private List<ValidationError> findValidationErrors(double y) {
List<ValidationError> errors = new ArrayList<>();
if (model.get() != null) {
List<ValidationError> errorList = model.get().getValidationError();
if (errorList != null && !errorList.isEmpty()) {
int rows = model.get().getRows().size();
double space = ((int)canvas.getHeight()) / rows;
for (ValidationError error : errorList) {
double blockStart = space * error.getLineNumber();
if (blockStart-1 <= y && y <= blockStart+3) {
errors.add(error);
}
}
}
}
return errors;
}
private void drawErrorMarker(int w, int h) {
GraphicsContext g = canvas.getGraphicsContext2D();
g.clearRect(0, 0, w, h);
g.setFill(Color.valueOf("#ff8888"));
if (model.get() != null) {
List<ValidationError> errorList = model.get().getValidationError();
if (errorList != null && !errorList.isEmpty()) {
int rows = model.get().getRows().size();
double space = h / rows;
for (ValidationError error : errorList) {
double blockStart = space * error.getLineNumber();
g.fillRect(0, blockStart, w, 2);
}
}
}
}
private Node popupContent(String text) {
VBox vBox = new VBox();
vBox.setPadding(new Insets(10,10,10,10));
vBox.getChildren().add(new Text(text));
return vBox;
}
}

View File

@@ -1,70 +0,0 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.list;
import javafx.scene.control.ListCell;
import javafx.scene.text.Text;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.util.ResourceBundle;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/**
* Cell to show the error text
*/
public class ValidationErrorListCell extends ListCell<ValidationError> {
private ResourceBundle resourceBundle;
public ValidationErrorListCell(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
@Override
public void updateItem(ValidationError validationError, boolean empty) {
super.updateItem(validationError, empty);
if (empty) {
clearContent();
} else {
addContent(validationError);
}
}
private void clearContent() {
setText(null);
setGraphic(null);
}
private void addContent(ValidationError validationError) {
setText(null);
Text text = new Text(getI18nValidatioMessage(resourceBundle, validationError));
text.setWrappingWidth(180);
setGraphic(text);
}
}

View File

@@ -146,3 +146,10 @@
-fx-padding: 0 5.417em 0 0;
}
.tooltip {
-fx-background-radius: 2 2 2 2;
-fx-background-color: linear-gradient(#eeeeee, #aaaaaa);
-fx-text-fill: black;
-fx-font-size: 12px;
-fx-padding: 10 10 10 10;
}

View File

@@ -1,17 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import de.jensd.fx.glyphs.fontawesome.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1">
<?import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView?>
<?import ninja.javafx.smartcsv.fx.list.ErrorSideBar?>
<BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="1000.0" minHeight="700.0" minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<VBox prefWidth="100.0" BorderPane.alignment="CENTER" id="background">
<VBox id="background" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<MenuBar>
<menus>
@@ -103,7 +119,7 @@
<FontAwesomeIconView styleClass="save-icon" />
</graphic>
</Button>
<Button fx:id="saveAsButton" disable="true" mnemonicParsing="false" onAction="#saveAsCsv" text="..." styleClass="last">
<Button fx:id="saveAsButton" disable="true" mnemonicParsing="false" onAction="#saveAsCsv" styleClass="last" text="...">
<tooltip>
<Tooltip text="%menu.save.as" />
</tooltip>
@@ -114,7 +130,7 @@
</HBox>
<Region styleClass="spacer" />
<HBox styleClass="segmented-button-bar">
<Button fx:id="deleteRowButton" disable="true" mnemonicParsing="false" onAction="#deleteRow" styleClass="first">
<Button fx:id="deleteRowButton" disable="true" mnemonicParsing="false" onAction="#deleteRow" styleClass="first">
<tooltip>
<Tooltip text="%menu.delete.row" />
</tooltip>
@@ -122,7 +138,7 @@
<FontAwesomeIconView styleClass="delete-icon" />
</graphic>
</Button>
<Button fx:id="addRowButton" disable="true" mnemonicParsing="false" onAction="#addRow" styleClass="last">
<Button fx:id="addRowButton" disable="true" mnemonicParsing="false" onAction="#addRow" styleClass="last">
<tooltip>
<Tooltip text="%menu.add.row" />
</tooltip>
@@ -155,29 +171,12 @@
</VBox>
</top>
<center>
<SplitPane dividerPositions="0.8" prefHeight="160.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<AnchorPane fx:id="tableWrapper">
<TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" BorderPane.alignment="CENTER">
<columns>
</columns>
</TableView>
</AnchorPane>
<VBox>
<children>
<Label text="%title.validation.errors">
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
</padding>
<graphic>
<FontAwesomeIconView styleClass="error-title-icon" />
</graphic>
</Label>
<ListView fx:id="errorList" minWidth="10.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
</children>
</VBox>
</items>
</SplitPane>
<AnchorPane fx:id="tableWrapper">
<TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" BorderPane.alignment="CENTER">
<columns>
</columns>
</TableView>
</AnchorPane>
</center>
<left>
</left>