Commit 97a65114 authored by Riegel's avatar Riegel
Browse files

Merge branch 'dev' into 'master'

Version 3.15.0

See merge request !8
parents 99c8f6a8 5950ea5f
Pipeline #10106 passed with stage
in 3 minutes and 15 seconds
package de.hft.stuttgart.citydoctor2.gui;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
public class LoadingInfoDialog {
private Stage stage;
public LoadingInfoDialog(Window parent) throws IOException {
FXMLLoader loader = new FXMLLoader(LoadingInfoDialog.class.getResource("CreateRenderDataDialog.fxml"));
loader.setController(this);
VBox box = loader.load();
stage = new Stage(StageStyle.UNDECORATED);
Scene scene = new Scene(box);
scene.setFill(null);
stage.setScene(scene);
stage.initOwner(parent);
stage.initModality(Modality.APPLICATION_MODAL);
}
public void show() {
stage.show();
}
public void hide() {
stage.hide();
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.IOException;
import java.io.InputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.gui.tree.Renderable;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MainToolBar {
private static final Logger logger = LogManager.getLogger(MainToolBar.class);
@FXML
private Button saveBtn;
@FXML
private ImageView saveView;
@FXML
private Button openBtn;
@FXML
private ImageView openImageView;
@FXML
private Button checkBtn;
@FXML
private ImageView checkImageView;
@FXML
private Button showWorldBtn;
@FXML
private ImageView showWorldImageView;
@FXML
private Button schematronBtn;
@FXML
private ImageView schematronImgView;
@FXML
private ToggleButton lod1Btn;
@FXML
private ImageView lod1View;
@FXML
private ToggleButton lod2Btn;
@FXML
private ImageView lod2View;
@FXML
private ToggleButton lod3Btn;
@FXML
private ImageView lod3View;
@FXML
private ToggleButton lod4Btn;
@FXML
private ImageView lod4View;
@FXML
private Button aboutBtn;
@FXML
private ImageView aboutImgView;
@FXML
private ToggleButton gridButton;
@FXML
private ToggleButton cullingButton;
@FXML
private ImageView cullingImageView;
@FXML
private ImageView gridImageView;
@FXML
private Button reportBtn;
@FXML
private ImageView reportImageView;
private OpenFileDialog fileDialog;
private CheckDialog checkDialog;
private WriteReportDialog writeDialog;
private AboutDialog aboutDialog;
private CityDoctorController controller;
private TabPane featurePane;
private Stage stage;
private Renderer renderer;
private MainWindow mainWindow;
private HBox toolBar;
public MainToolBar(Stage stage, CityDoctorController controller, TabPane featurePane, Renderer renderer,
MainWindow mainWindow) throws IOException {
this.controller = controller;
this.featurePane = featurePane;
this.renderer = renderer;
this.stage = stage;
this.mainWindow = mainWindow;
FXMLLoader loader = new FXMLLoader(MainToolBar.class.getResource("MainToolBar.fxml"));
loader.setController(this);
toolBar = loader.load();
fileDialog = new OpenFileDialog(stage, controller);
}
public void initialize() {
openBtn.setOnAction(ae -> fileDialog.show());
setupSaveBtn();
setupCheckButton();
setupLodButtons();
setupAboutButton();
setupReportButton();
loadImages();
gridButton.setOnAction(ae -> renderer.showWireFrame(gridButton.isSelected()));
gridButton.setTooltip(new Tooltip(Localization.getText("MainToolBar.wireframe")));
cullingButton.setOnAction(ae -> renderer.enableCulling(cullingButton.isSelected()));
cullingButton.setTooltip(new Tooltip(Localization.getText("MainToolBar.culling")));
showWorldBtn.setOnAction(ar -> {
Tab selectedItem = featurePane.getSelectionModel().getSelectedItem();
if (selectedItem.getContent() instanceof TreeView) {
@SuppressWarnings("unchecked")
TreeView<Renderable> content = (TreeView<Renderable>) selectedItem.getContent();
content.getSelectionModel().clearSelection();
}
controller.showWorld();
});
}
private void loadImages() {
try {
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/openFolderIcon.png")) {
Image img = new Image(inStream);
openImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/check32x32.png")) {
Image img = new Image(inStream);
checkImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/wireframe32x32.png")) {
Image img = new Image(inStream);
gridImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/Culling.png")) {
Image img = new Image(inStream);
cullingImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/error_stat32x32.png")) {
Image img = new Image(inStream);
reportImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/scene.png")) {
Image img = new Image(inStream);
showWorldImageView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/lod1_32x32.png")) {
Image img = new Image(inStream);
lod1View.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/lod2_32x32.png")) {
Image img = new Image(inStream);
lod2View.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/lod3_32x32.png")) {
Image img = new Image(inStream);
lod3View.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/lod4_32x32.png")) {
Image img = new Image(inStream);
lod4View.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/about.png")) {
Image img = new Image(inStream);
aboutImgView.setImage(img);
}
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/save.png")) {
Image img = new Image(inStream);
saveView.setImage(img);
}
} catch (IOException e) {
// ignore close exception
}
}
private void setupReportButton() {
reportBtn.setDisable(true);
reportBtn.setTooltip(new Tooltip(Localization.getText("MainToolBar.writeReports")));
reportBtn.setOnAction(ae -> {
if (writeDialog == null) {
try {
writeDialog = new WriteReportDialog(stage, controller, mainWindow);
} catch (IOException e) {
logger.catching(e);
mainWindow.showExceptionDialog(e);
}
}
if (writeDialog != null) {
// writeDialog can be null if creation of said dialog fails
writeDialog.show();
}
});
}
private void setupAboutButton() {
aboutBtn.setOnAction(ae -> {
if (aboutDialog == null) {
try {
aboutDialog = new AboutDialog(stage);
aboutDialog.show();
} catch (IOException e) {
logger.error("Could not load about dialog.", e);
}
} else {
aboutDialog.show();
}
});
}
private void setupLodButtons() {
lod1Btn.setOnAction(ae -> {
if (lod1Btn.isSelected()) {
renderer.enableLod1();
} else {
renderer.disableLod1();
}
});
lod2Btn.setOnAction(ae -> {
if (lod2Btn.isSelected()) {
renderer.enableLod2();
} else {
renderer.disableLod2();
}
});
lod3Btn.setOnAction(ae -> {
if (lod3Btn.isSelected()) {
renderer.enableLod3();
} else {
renderer.disableLod3();
}
});
lod4Btn.setOnAction(ae -> {
if (lod4Btn.isSelected()) {
renderer.enableLod4();
} else {
renderer.disableLod4();
}
});
}
private void setupSaveBtn() {
saveBtn.setOnAction(ae -> controller.askAndSave());
}
private void setupCheckButton() {
checkBtn.setDisable(true);
checkBtn.setTooltip(new Tooltip(Localization.getText("MainToolBar.executeChecks")));
checkBtn.setOnAction(ae -> {
if (checkDialog == null) {
try {
checkDialog = new CheckDialog(mainWindow, stage, controller);
} catch (IOException e) {
mainWindow.showExceptionDialog(e);
logger.catching(e);
}
}
checkDialog.show();
});
}
public Button getCheckButton() {
return checkBtn;
}
public ToggleButton getGridButton() {
return gridButton;
}
public ToggleButton getCullingButton() {
return cullingButton;
}
public Button getWriteReportButton() {
return reportBtn;
}
public ToggleButton getLod1Btn() {
return lod1Btn;
}
public ToggleButton getLod2Btn() {
return lod2Btn;
}
public ToggleButton getLod3Btn() {
return lod3Btn;
}
public ToggleButton getLod4Btn() {
return lod4Btn;
}
public Button getWorldBtn() {
return showWorldBtn;
}
public Button getSaveBtn() {
return saveBtn;
}
public HBox getToolBar() {
return toolBar;
}
public Button getOpenBtn() {
return openBtn;
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.CityDoctorValidation;
import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.FeatureType;
import de.hft.stuttgart.citydoctor2.gui.logger.GuiLogger;
import de.hft.stuttgart.citydoctor2.gui.tree.BuildingNode;
import de.hft.stuttgart.citydoctor2.gui.tree.Renderable;
import de.hft.stuttgart.citydoctor2.gui.tree.RenderableTreeCell;
import de.hft.stuttgart.citydoctor2.parameter.ArgumentParser;
import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException;
import de.hft.stuttgart.citydoctor2.parser.ProgressListener;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.AmbientLight;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class MainWindow extends Application {
private static final Logger logger = LogManager.getLogger(MainWindow.class);
private static final double CAMERA_TRANSLATE_Z = -100.0;
private static final double CAMERA_INITIAL_X_ANGLE = 20.0;
private static final double CAMERA_INITIAL_Y_ANGLE = 120.0;
@FXML
private TreeView<Renderable> buildingsView;
@FXML
private TreeView<Renderable> vegetationView;
@FXML
private TreeView<Renderable> transView;
@FXML
private TreeView<Renderable> bridgeView;
@FXML
private TreeView<Renderable> waterView;
@FXML
private TreeView<Renderable> terrainView;
@FXML
private TreeView<Renderable> polygonView;
@FXML
private TreeView<Renderable> edgeView;
@FXML
private TreeView<Renderable> vertexView;
@FXML
private Pane meshView;
@FXML
private SplitPane mainContainer;
@FXML
private TabPane detailsTabPane;
@FXML
private TabPane featurePane;
@FXML
private TextField searchField;
@FXML
private TreeView<Renderable> errorView;
@FXML
private Button clearBtn;
@FXML
private Button searchBtn;
@FXML
private ToggleButton allButton;
@FXML
private ToggleButton errorButton;
@FXML
private ComboBox<String> showCityObjectsCombo;
@FXML
private HBox viewPane;
@FXML
private ListView<String> globalErrorsView;
@FXML
private BorderPane mainPane;
@FXML
private HBox viewButtonBox;
@FXML
private ComboBox<Locale> languageSelector;
@FXML
private Tab buildingsTab;
@FXML
private Tab vegetationTab;
@FXML
private Tab transportationTab;
@FXML
private Tab bridgeTab;
@FXML
private Tab waterTab;
@FXML
private Tab terrainTab;
@FXML
private Tab errorsTab;
@FXML
private Tab polygonsTab;
@FXML
private Tab edgesTab;
@FXML
private Tab verticesTab;
@FXML
private Tab logTab;
@FXML
private Tab globalErrorsTab;
@FXML
private Label viewLabel;
@FXML
private Label showLabel;
@FXML
private Label searchLabel;
@FXML
private Label memoryLabel;
@FXML
private ProgressBar memoryBar;
@FXML
private Label memoryConsumptionLabel;
@FXML
private Label availableLabel;
private Group meshGroup;
private Group world;
private PerspectiveCamera camera;
private Rotate cameraXRotation = new Rotate();
private Rotate cameraZRotation = new Rotate();
private double dragX;
private double dragY;
private double cameraXRot = CAMERA_INITIAL_X_ANGLE;
private double cameraYRot = CAMERA_INITIAL_Y_ANGLE;
private double translateZ = CAMERA_TRANSLATE_Z;
private double[] clickStart = new double[2];
@FXML
private TextArea logArea;
@FXML
private ToolBar viewBar;
private Stage stage;
private ExceptionDialog exceptionDialog;
private Renderer renderer;
private CityDoctorController controller;
private HighlightController highlightController;
private FeatureType selectedTab = FeatureType.BUILDING;
private MainToolBar mainToolBar;
private SubScene geomScene;
private static boolean loadFileAtStartup = false;
private static String inputFile;
private static String xmlOutput;
private static String pdfOutput;
private static ValidationConfiguration config;
private VertexClickHandler clickHandler;
private ChangeListener<Number> filterChangeListener;
public static void main(String[] args) {
setLocaleFromSettings();
ArgumentParser argParser = new ArgumentParser(args);
inputFile = CityDoctorValidation.getInputFile(argParser, true);
if (inputFile != null) {
loadFileAtStartup = true;
xmlOutput = CityDoctorValidation.getXmlOutput(argParser);
pdfOutput = CityDoctorValidation.getPdfOutput(argParser);
try {
config = CityDoctorValidation.getValidationConfig(argParser, true);
} catch (FileNotFoundException e) {
Platform.runLater(() -> {
List<String> configFiles = argParser.getValues("config");
Alert alert = new Alert(AlertType.ERROR);
alert.setContentText(Localization.getText("MainWindow.missingConfig") + configFiles);
alert.showAndWait();
});
System.exit(4);
}
}
Application.launch(args);
}
private static void setLocaleFromSettings() {
String localeString = Settings.get(Settings.LANGUAGE);
if (localeString != null) {
try {
Locale loc = new Locale(localeString);
Locale.setDefault(loc);
} catch (Exception e) {
logger.warn("Could not set language to {}, using system language", localeString);
}
}
}
@Override
public void start(Stage stage) throws IOException {
this.stage = stage;
stage.getIcons().add(new Image(MainWindow.class.getResourceAsStream("icons/citydoctor_logo.png")));
FXMLLoader loader = new FXMLLoader(MainWindow.class.getResource("MainWindow.fxml"));
loader.setController(this);
BorderPane bp = loader.load();
highlightController = new HighlightController(world);
renderer = new Renderer(this, highlightController);
clickHandler = new VertexClickHandler(errorView, renderer, stage);
controller = new CityDoctorController(this, highlightController, renderer);
mainToolBar = new MainToolBar(stage, controller, featurePane, renderer, this);
viewPane.getChildren().add(mainToolBar.getToolBar());
HBox.setHgrow(mainToolBar.getToolBar(), Priority.ALWAYS);
ValidationView valView = new ValidationView(this, controller);
ViewRegistration.registerView(valView);
setupViews(valView);
createLanguageSelector();
setLabelsInCorrectLanguage();
Scene scene = new Scene(bp, 1280, 800);
createDropTarget(scene, valView);
String version = Localization.getText(Localization.VERSION);
stage.setTitle("CityDoctor " + version);
stage.setScene(scene);
stage.show();
checkForStartupLoading();
memoryBar.setOnMouseClicked(me -> System.gc());
Timer timer = new Timer(true);
// check memory every second
TimerTask task = new TimerTask() {
Runtime runtime = Runtime.getRuntime();
@Override
public void run() {
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
double percentage = usedMemory / (double) totalMemory;
if (totalMemory / 1024 / 1024 >= 1024) {
// gb
double totalMemoryGb = totalMemory / (1024d * 1024d * 1024d);
double usedMemoryGb = usedMemory / (1024d * 1024d * 1024d);
String memoryString = String.format("%.1f GB / %.1f GB", usedMemoryGb, totalMemoryGb);
Platform.runLater(() -> {
memoryConsumptionLabel.setText(memoryString);
memoryBar.setProgress(percentage);
});
} else if (totalMemory / 1024 >= 1024) {
// mb
double totalMemoryMb = totalMemory / (1024d * 1024d);
double usedMemoryMb = usedMemory / (1024d * 1024d);
String memoryString = String.format("%.1f MB / %.1f MB", usedMemoryMb, totalMemoryMb);
Platform.runLater(() -> {
memoryConsumptionLabel.setText(memoryString);
memoryBar.setProgress(percentage);
});
}
}
};
timer.schedule(task, 0, 1000);
}
private void createDropTarget(Scene scene, ValidationView valView) {
setDragOverInteraction(scene, valView);
scene.setOnDragDropped(event -> {
if (ViewRegistration.getCurrentActiveView() != valView) {
return;
}
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasFiles() && db.getFiles().size() == 1) {
File f = db.getFiles().get(0);
Thread t = new Thread(() -> {
try {
controller.loadCityGml(f.getAbsolutePath(), 8, (ProgressListener) null, false);
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(Localization.getText("OpenFileDialog.loadFailed"), e);
}
Platform.runLater(() -> {
ExceptionDialog exDialog = new ExceptionDialog();
exDialog.show(e);
});
}
});
t.start();
success = true;
}
// let the source know whether the string was successfully transferred and used
event.setDropCompleted(success);
event.consume();
});
}
private void setDragOverInteraction(Scene scene, ValidationView valView) {
scene.setOnDragOver(event -> {
if (ViewRegistration.getCurrentActiveView() != valView) {
return;
}
if (event.getGestureSource() != scene && event.getDragboard().hasFiles()
&& event.getDragboard().getFiles().size() == 1) {
// allow for both copying and moving, whatever user chooses
event.acceptTransferModes(TransferMode.LINK);
}
event.consume();
});
}
private void setLabelsInCorrectLanguage() {
buildingsTab.setText(Localization.getText("MainWindow.buildingsTab"));
vegetationTab.setText(Localization.getText("MainWindow.vegetationTab"));
transportationTab.setText(Localization.getText("MainWindow.transportationTab"));
bridgeTab.setText(Localization.getText("MainWindow.bridgeTab"));
waterTab.setText(Localization.getText("MainWindow.waterTab"));
terrainTab.setText(Localization.getText("MainWindow.terrainTab"));
viewLabel.setText(Localization.getText("MainWindow.viewLabel"));
showLabel.setText(Localization.getText("MainWindow.showLabel"));
searchLabel.setText(Localization.getText("MainWindow.searchLabel"));
searchBtn.setText(Localization.getText("MainWindow.searchBtn"));
clearBtn.setText(Localization.getText("MainWindow.clearBtn"));
errorsTab.setText(Localization.getText("MainWindow.errorsTab"));
polygonsTab.setText(Localization.getText("MainWindow.polygonsTab"));
edgesTab.setText(Localization.getText("MainWindow.edgesTab"));
verticesTab.setText(Localization.getText("MainWindow.verticesTab"));
logTab.setText(Localization.getText("MainWindow.logTab"));
globalErrorsTab.setText(Localization.getText("MainWindow.globalErrorsTab"));
memoryLabel.setText(Localization.getText("MainWindow.memoryLabel"));
availableLabel.setText(String.format("%s %.1f GB", Localization.getText("MainWindow.availableLabel"),
Runtime.getRuntime().maxMemory() / 1024d / 1024d / 1024d));
}
private void createLanguageSelector() {
languageSelector.setButtonCell(new LanguageSelectorCell());
languageSelector.setCellFactory(view -> new LanguageSelectorCell());
languageSelector.getItems().add(Locale.GERMAN);
languageSelector.getItems().add(Locale.ENGLISH);
if (Locale.getDefault().getLanguage().equals(Locale.GERMAN.getLanguage())) {
languageSelector.getSelectionModel().select(Locale.GERMAN);
} else if (Locale.getDefault().getLanguage().equals(Locale.ENGLISH.getLanguage())) {
languageSelector.getSelectionModel().select(Locale.ENGLISH);
} else {
languageSelector.getSelectionModel().select(Locale.ENGLISH);
Settings.set(Settings.LANGUAGE, Locale.ENGLISH.getLanguage());
}
languageSelector.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
Alert alert = new Alert(AlertType.CONFIRMATION, Localization.getText("MainWindow.languageChange"),
ButtonType.OK);
alert.showAndWait();
Settings.set(Settings.LANGUAGE, newV.getLanguage());
});
}
private void checkForStartupLoading() {
if (loadFileAtStartup) {
logger.info(Localization.getText("MainWindow.loadGivenFile"));
Thread t = new Thread(() -> {
try {
// wait a bit for the gui to show
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
try {
controller.loadCityGml(inputFile, config.getNumberOfRoundingPlaces(), null,
config.isXmlValidation());
logger.info(Localization.getText("MainWindow.finishedLoading"));
logger.info(Localization.getText("MainWindow.checking"));
controller.startChecks(config, null);
if (xmlOutput != null) {
logger.info(Localization.getText("MainWindow.writeXml"));
controller.writeXmlReport(new File(xmlOutput));
logger.info(Localization.getText("MainWindow.finishedXml"));
}
if (pdfOutput != null) {
logger.info(Localization.getText("MainWindow.writePdf"));
controller.writePdfReport(new File(pdfOutput));
logger.info(Localization.getText("MainWindow.finishedPdf"));
}
} catch (CityGmlParseException | InvalidGmlFileException e) {
logger.error(Localization.getText("MainWindow.loadFailed"), e.getMessage());
}
});
t.start();
}
}
private void setupViews(ValidationView valView) {
ToggleGroup group = new ToggleGroup();
for (View v : ViewRegistration.getRegisteredViews()) {
RadioButton radioButton = new RadioButton();
radioButton.getStyleClass().remove("radio-button");
radioButton.getStyleClass().add("toggle-button");
ImageView view = new ImageView(v.getViewLogo());
view.setFitHeight(32);
view.setFitWidth(32);
radioButton.setGraphic(view);
group.getToggles().add(radioButton);
if (v == valView) {
ViewRegistration.setCurrentActiveView(valView);
group.selectToggle(radioButton);
}
viewButtonBox.getChildren().add(radioButton);
radioButton.selectedProperty().addListener((obs, oldV, newV) -> {
if (Boolean.TRUE.equals(newV)) {
showView(v);
ViewRegistration.setCurrentActiveView(v);
} else {
v.onHide();
}
});
}
}
private void showView(View v) {
viewPane.getChildren().clear();
controller.showView(v);
Optional<HBox> toolbar = v.getToolbar();
if (toolbar.isPresent()) {
viewPane.getChildren().add(toolbar.get());
HBox.setHgrow(toolbar.get(), Priority.ALWAYS);
} else {
HBox placeHolderToolbar = new HBox();
placeHolderToolbar.setMaxHeight(Double.MAX_VALUE);
viewPane.getChildren().add(placeHolderToolbar);
HBox.setHgrow(placeHolderToolbar, Priority.ALWAYS);
}
mainPane.setCenter(v.getMainScreen());
}
public void initialize() {
GuiLogger.setTextArea(logArea);
loadFrameConfig();
setup3dView();
setupTrees();
setupSearchField();
setupSearchButtons();
setupShowCityComboBox();
detailsTabPane.getSelectionModel().selectedIndexProperty()
.addListener((ov, oldI, newI) -> Platform.runLater(() -> {
if (newI.intValue() == 0 && errorView.getSelectionModel().getSelectedItem() != null) {
// the first tab is selected, meaning the error tab
// redisplay the selected error
errorView.getSelectionModel().getSelectedItem().getValue().visit(renderer);
}
}));
}
private void setupShowCityComboBox() {
showCityObjectsCombo.getItems().addAll(Localization.getText("MainWindow.all"),
Localization.getText("MainWindow.withErrors"));
showCityObjectsCombo.getSelectionModel().selectFirst();
showCityObjectsCombo.setCellFactory(param -> new ListCell<>() {
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item);
if (item.contentEquals(Localization.getText("MainWindow.withErrors"))) {
setTextFill(Color.RED);
}
} else {
setText(null);
}
}
});
filterChangeListener = setupFilterSelectionListener();
showCityObjectsCombo.getSelectionModel().selectedIndexProperty().addListener(filterChangeListener);
}
public void resetFilterComboBox() {
showCityObjectsCombo.getSelectionModel().selectedIndexProperty().removeListener(filterChangeListener);
showCityObjectsCombo.getSelectionModel().selectFirst();
showCityObjectsCombo.getSelectionModel().selectedIndexProperty().addListener(filterChangeListener);
}
private ChangeListener<Number> setupFilterSelectionListener() {
return (obs, oldV, newV) -> controller.errorFilterIndexChanged(newV);
}
private void setupSearchButtons() {
searchBtn.setOnAction(ae -> controller.searchFeature(searchField.getText(), selectedTab));
clearBtn.setOnAction(ae -> {
if (searchField.getText().isEmpty()) {
// do not reset search if nothing has bee searched
return;
}
controller.resetSearch(selectedTab);
searchField.setText("");
});
}
private void setupSearchField() {
searchField.textProperty().addListener((obs, oldV, newV) -> {
if (newV.isEmpty()) {
controller.resetSearch(selectedTab);
}
});
featurePane.getSelectionModel().selectedIndexProperty().addListener((obs, oldV, newV) -> {
if (!searchField.getText().isEmpty()) {
resetSearchBar();
controller.resetSearch(selectedTab);
}
int index = newV.intValue();
switch (index) {
case 0:
selectedTab = FeatureType.BUILDING;
break;
case 1:
selectedTab = FeatureType.VEGETATION;
break;
case 2:
selectedTab = FeatureType.TRANSPORTATION;
break;
case 3:
selectedTab = FeatureType.BRIDGE;
break;
case 4:
selectedTab = FeatureType.WATER;
break;
case 5:
selectedTab = FeatureType.LAND;
break;
default:
throw new IllegalStateException("Unknown tab index: " + index);
}
});
}
public void resetSearchBar() {
searchField.setText("");
}
private void loadFrameConfig() {
stage.setMaximized(Boolean.valueOf(Settings.get(Settings.MAXIMIZED, Boolean.FALSE.toString())));
stage.maximizedProperty()
.addListener((obs, oldV, newV) -> Settings.set(Settings.MAXIMIZED, Boolean.toString(newV)));
String widthString = Settings.get(Settings.FRAME_WIDTH);
if (widthString != null) {
stage.setWidth(Double.parseDouble(widthString));
}
stage.widthProperty().addListener(
(obs, oldV, newV) -> Settings.set(Settings.FRAME_WIDTH, Double.toString(newV.doubleValue())));
String heightString = Settings.get(Settings.FRAME_HEIGHT);
if (heightString != null) {
stage.setHeight(Double.parseDouble(heightString));
}
stage.heightProperty().addListener(
(obs, oldV, newV) -> Settings.set(Settings.FRAME_HEIGHT, Double.toString(newV.doubleValue())));
}
public void showExceptionDialog(Throwable e) {
if (exceptionDialog == null) {
exceptionDialog = new ExceptionDialog();
}
exceptionDialog.show(e);
}
private void setupTrees() {
setupSelectListener(errorView);
errorView.setRoot(new TreeItem<>());
errorView.setCellFactory(param -> new RenderableTreeCell());
buildingsView.setShowRoot(true);
setupSelectListener(buildingsView);
buildingsView.setCellFactory(param -> new RenderableTreeCell());
ContextMenu cMenu = new ContextMenu();
MenuItem mi = new MenuItem(Localization.getText("MainWindow.export"));
mi.setOnAction(ea -> {
Renderable render = buildingsView.getSelectionModel().getSelectedItem().getValue();
if (render instanceof BuildingNode node) {
controller.export(node.getBuilding());
}
});
cMenu.getItems().add(mi);
MenuItem deleteMi = new MenuItem("Delete");
deleteMi.setOnAction(ae -> controller.delete(buildingsView.getSelectionModel().getSelectedItem()));
cMenu.getItems().add(deleteMi);
buildingsView.setContextMenu(cMenu);
vegetationView.setShowRoot(true);
setupSelectListener(vegetationView);
vegetationView.setCellFactory(param -> new RenderableTreeCell());
transView.setShowRoot(true);
setupSelectListener(transView);
transView.setCellFactory(param -> new RenderableTreeCell());
bridgeView.setShowRoot(true);
setupSelectListener(bridgeView);
bridgeView.setCellFactory(param -> new RenderableTreeCell());
waterView.setShowRoot(true);
setupSelectListener(waterView);
waterView.setCellFactory(param -> new RenderableTreeCell());
terrainView.setShowRoot(true);
setupSelectListener(terrainView);
terrainView.setCellFactory(param -> new RenderableTreeCell());
setupSelectListener(vertexView);
vertexView.setRoot(new TreeItem<>());
vertexView.setCellFactory(param -> new RenderableTreeCell());
setupSelectListener(polygonView);
polygonView.setRoot(new TreeItem<>());
polygonView.setCellFactory(param -> new RenderableTreeCell());
setupSelectListener(edgeView);
edgeView.setRoot(new TreeItem<>());
edgeView.setCellFactory(param -> new RenderableTreeCell());
}
private void setupSelectListener(TreeView<Renderable> view) {
view.getSelectionModel().selectedItemProperty().addListener((obs, oldI, newI) -> {
if (newI != null) {
newI.getValue().visit(renderer);
}
});
}
private void setup3dView() {
Group root = new Group();
geomScene = new SubScene(root, 500, 300, true, SceneAntialiasing.BALANCED);
geomScene.heightProperty().bind(meshView.heightProperty());
geomScene.widthProperty().bind(meshView.widthProperty());
geomScene.setFill(Color.AZURE);
meshView.getChildren().add(geomScene);
geomScene.addEventFilter(MouseEvent.MOUSE_PRESSED, me -> {
clickStart[0] = me.getScreenX();
clickStart[1] = me.getScreenY();
});
geomScene.addEventFilter(MouseEvent.MOUSE_RELEASED, me -> {
if (Math.abs(clickStart[0] - me.getScreenX()) > 3 || Math.abs(clickStart[1] - me.getScreenY()) > 3) {
// skip when mouse moved too much
return;
}
Node node = me.getPickResult().getIntersectedNode();
if (node != null) {
Object o = node.getUserData();
if (o instanceof ClickDispatcher cd) {
cd.click(me, clickHandler);
}
}
});
world = new Group();
root.getChildren().add(world);
meshGroup = new Group();
world.getChildren().add(meshGroup);
AmbientLight al = new AmbientLight(Color.WHITE);
root.getChildren().add(al);
buildCamera();
cameraXRotation.setAxis(Rotate.X_AXIS);
cameraZRotation.setAxis(Rotate.Z_AXIS);
world.getTransforms().add(cameraXRotation);
world.getTransforms().add(cameraZRotation);
root.getChildren().add(camera);
geomScene.setCamera(camera);
setupMeshViewControls();
}
private void setupMeshViewControls() {
meshView.setOnMousePressed(me -> {
if (me.getButton() == MouseButton.PRIMARY) {
dragX = me.getScreenX();
dragY = me.getScreenY();
}
});
meshView.setOnScroll(se -> {
if (se.getDeltaY() < 0) {
translateZ += translateZ * 0.05;
} else {
translateZ -= translateZ * 0.05;
}
camera.setTranslateZ(translateZ);
highlightController.changeScaling(translateZ);
});
meshView.setOnMouseDragged(me -> {
if (me.getButton() == MouseButton.PRIMARY) {
double deltaX = me.getScreenX() - dragX;
double deltaY = me.getScreenY() - dragY;
dragX = me.getScreenX();
dragY = me.getScreenY();
cameraXRot += (deltaX / 3d) % 360;
cameraYRot += (deltaY / 3d) % 360;
cameraZRotation.setAngle(cameraXRot);
cameraXRotation.setAngle(cameraYRot);
}
});
}
private void buildCamera() {
camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000d);
camera.setTranslateZ(translateZ);
cameraZRotation.setAngle(cameraXRot);
cameraXRotation.setAngle(cameraYRot);
}
public void addFileNameToTitle(String fileName) {
String version = Localization.getText(Localization.VERSION);
stage.setTitle("CityDoctor " + version + " - " + fileName);
}
public TreeView<Renderable> getBuildingsView() {
return buildingsView;
}
public TreeView<Renderable> getVegetationView() {
return vegetationView;
}
public TreeView<Renderable> getTransportationView() {
return transView;
}
public TreeView<Renderable> getBridgeView() {
return bridgeView;
}
public TreeView<Renderable> getWaterView() {
return waterView;
}
public TreeView<Renderable> getTerrainView() {
return terrainView;
}
public TreeView<Renderable> getPolygonsView() {
return polygonView;
}
public TreeView<Renderable> getEdgeView() {
return edgeView;
}
public TreeView<Renderable> getVertexView() {
return vertexView;
}
public Button getCheckButton() {
return mainToolBar.getCheckButton();
}
public ToggleButton getGridButton() {
return mainToolBar.getGridButton();
}
public Group getMeshGroup() {
return meshGroup;
}
public ToggleButton getCullingButton() {
return mainToolBar.getCullingButton();
}
public Button getWriteReportButton() {
return mainToolBar.getWriteReportButton();
}
public TreeView<Renderable> getErrorTree() {
return errorView;
}
public Stage getMainStage() {
return stage;
}
public void unselectEverything() {
buildingsView.getSelectionModel().clearSelection();
vegetationView.getSelectionModel().clearSelection();
transView.getSelectionModel().clearSelection();
waterView.getSelectionModel().clearSelection();
terrainView.getSelectionModel().clearSelection();
bridgeView.getSelectionModel().clearSelection();
polygonView.getSelectionModel().clearSelection();
edgeView.getSelectionModel().clearSelection();
vertexView.getSelectionModel().clearSelection();
errorView.getSelectionModel().clearSelection();
}
public ToggleButton getLod1Btn() {
return mainToolBar.getLod1Btn();
}
public ToggleButton getLod2Btn() {
return mainToolBar.getLod2Btn();
}
public ToggleButton getLod3Btn() {
return mainToolBar.getLod3Btn();
}
public ToggleButton getLod4Btn() {
return mainToolBar.getLod4Btn();
}
public Button getWorldBtn() {
return mainToolBar.getWorldBtn();
}
public Button getSaveBtn() {
return mainToolBar.getSaveBtn();
}
public SplitPane getMainContainer() {
return mainContainer;
}
public ListView<String> getGlobalErrorsView() {
return globalErrorsView;
}
public void takeViewScreenshot() throws IOException {
WritableImage snapshot = geomScene.snapshot(null, null);
File outputFile = new File("img.png");
BufferedImage bImage = SwingFXUtils.fromFXImage(snapshot, null);
ImageIO.write(bImage, "png", outputFile);
}
public void zoomOutForBoundingBox(BoundingBox b) {
double longestSide = b.getDiagonalLength() * 0.4;
double d = longestSide / Math.tan(Math.toRadians(30) / 2);
translateZ = -d;
camera.setTranslateZ(translateZ);
highlightController.changeScaling(-translateZ);
}
public MainToolBar getMainToolbar() {
return mainToolBar;
}
public void clearHighlights() {
highlightController.clearHighlights();
}
public CityDoctorController getController() {
return controller;
}
public Button getOpenBtn() {
return mainToolBar.getOpenBtn();
}
public VertexClickHandler getClickHandler() {
return clickHandler;
}
public FeatureType getSelectedTab() {
return selectedTab;
}
}
package de.hft.stuttgart.citydoctor2.gui;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
public class ModelProvider {
private CityDoctorController controller;
public ModelProvider(CityDoctorController controller) {
this.controller = controller;
}
public CityDoctorModel getModel() {
return controller.getModel();
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.File;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TitledPane;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class OpenFileDialog {
private static final Logger logger = LogManager.getLogger(OpenFileDialog.class);
private Stage stage;
@FXML
private Button loadBtn;
@FXML
private Button cancelBtn;
@FXML
private Button selectBtn;
@FXML
private TextField precisionField;
@FXML
private TextField pathField;
@FXML
private ProgressBar progress;
@FXML
private CheckBox useValidationBox;
@FXML
private Label fileLabel;
@FXML
private TitledPane settingsPane;
@FXML
private Label roundingPlacesLabel;
@FXML
private Label xmlValidationLabel;
@FXML
private Label lowMemoryLabel;
@FXML
private CheckBox lowMemoryBox;
private CityDoctorController controller;
private ExceptionDialog exDialog;
private FileChooser fc;
public OpenFileDialog(Window parent, CityDoctorController controller) throws IOException {
FXMLLoader loader = new FXMLLoader(OpenFileDialog.class.getResource("OpenFileDialog.fxml"));
loader.setController(this);
VBox box = loader.load();
this.controller = controller;
stage = new Stage();
stage.getIcons().add(new Image(MainWindow.class.getResourceAsStream("icons/CityDoctor-Logo-rot_klein.jpg")));
stage.setScene(new Scene(box));
stage.initOwner(parent);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Open File");
stage.getScene().addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
if (event.getCode() == KeyCode.ESCAPE) {
stage.close();
}
});
}
public void initialize() {
cancelBtn.setOnAction(ae -> stage.close());
setupPrecisionField();
setupLoadButton();
setupSelectButton();
applyLanguageToControls();
}
private void applyLanguageToControls() {
fileLabel.setText(Localization.getText("OpenFileDialog.fileLabel"));
selectBtn.setText(Localization.getText("OpenFileDialog.selectBtn"));
loadBtn.setText(Localization.getText("OpenFileDialog.loadBtn"));
settingsPane.setText(Localization.getText("OpenFileDialog.settingsPane"));
roundingPlacesLabel.setText(Localization.getText("OpenFileDialog.roundingPlacesLabel"));
xmlValidationLabel.setText(Localization.getText("OpenFileDialog.xmlValidationLabel"));
cancelBtn.setText(Localization.getText("OpenFileDialog.cancelBtn"));
lowMemoryLabel.setText(Localization.getText("OpenFileDialog.lowMemoryLabel"));
}
private void setupSelectButton() {
selectBtn.setOnAction(ae -> {
if (fc == null) {
fc = new FileChooser();
fc.setTitle(Localization.getText("OpenFileDialog.select"));
fc.getExtensionFilters().add(new ExtensionFilter("GML/XML", "*.gml", "*.xml"));
fc.getExtensionFilters().add(new ExtensionFilter(Localization.getText("MainWindow.all"), "*.*"));
}
File dir = new File(Settings.get(Settings.LAST_OPEN_FOLDER, ""));
if (dir.exists() && dir.isDirectory()) {
fc.setInitialDirectory(dir);
} else {
String userDir = System.getProperty("user.dir");
Settings.set(Settings.LAST_OPEN_FOLDER, userDir);
fc.setInitialDirectory(new File(userDir));
}
File f = fc.showOpenDialog(stage);
if (f != null) {
Settings.set(Settings.LAST_OPEN_FOLDER, f.getParent());
pathField.setText(f.getAbsolutePath());
}
});
}
private void setupLoadButton() {
loadBtn.setOnAction(ae -> {
int numberOfRoundingPlaces = Integer.parseInt(precisionField.getText());
boolean useValidation = useValidationBox.isSelected();
boolean lowMemory = lowMemoryBox.isSelected();
String path = pathField.getText();
cancelBtn.setDisable(true);
loadBtn.setDisable(true);
pathField.setDisable(true);
selectBtn.setDisable(true);
stage.setOnCloseRequest(Event::consume);
Thread t = new Thread(() -> {
try {
controller.loadCityGml(path, numberOfRoundingPlaces, progress::setProgress, useValidation,
lowMemory);
Platform.runLater(() -> stage.close());
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(Localization.getText("OpenFileDialog.loadFailed"), e);
}
Platform.runLater(() -> {
if (exDialog == null) {
exDialog = new ExceptionDialog();
}
exDialog.show(e);
});
} finally {
selectBtn.setDisable(false);
pathField.setDisable(false);
cancelBtn.setDisable(false);
loadBtn.setDisable(false);
stage.setOnCloseRequest(null);
}
});
t.start();
});
}
private void setupPrecisionField() {
TextFormatter<String> formatter = new TextFormatter<>(change -> {
if (!change.isContentChange()) {
return change;
}
String text = change.getControlNewText();
try {
Integer.parseInt(text);
return change;
} catch (NumberFormatException e) {
return null;
}
});
precisionField.setTextFormatter(formatter);
}
public void show() {
Platform.runLater(() -> {
progress.setProgress(0d);
stage.showAndWait();
});
}
}
package de.hft.stuttgart.citydoctor2.gui;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import javafx.scene.input.MouseEvent;
public class PolygonClickDispatcher implements ClickDispatcher {
private Polygon p;
public PolygonClickDispatcher(Polygon p) {
this.p = p;
}
@Override
public void click(MouseEvent me, ClickHandler handler) {
handler.onPolygonClick(p, me);
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.Checkable;
import de.hft.stuttgart.citydoctor2.datastructure.AbstractBuilding;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundingBox;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeConstructiveElement;
import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Lod;
import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.ReliefObject;
import de.hft.stuttgart.citydoctor2.datastructure.TinObject;
import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject;
import de.hft.stuttgart.citydoctor2.datastructure.Vegetation;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.WaterObject;
import de.hft.stuttgart.citydoctor2.gui.filter.ViewFilter;
import de.hft.stuttgart.citydoctor2.gui.tree.EdgeNode;
import de.hft.stuttgart.citydoctor2.gui.tree.ErrorItemVisitor;
import de.hft.stuttgart.citydoctor2.gui.tree.ErrorNode;
import de.hft.stuttgart.citydoctor2.gui.tree.LinearRingNode;
import de.hft.stuttgart.citydoctor2.gui.tree.PolygonNode;
import de.hft.stuttgart.citydoctor2.gui.tree.Renderable;
import de.hft.stuttgart.citydoctor2.gui.tree.VertexNode;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import javafx.application.Platform;
import javafx.scene.control.TreeItem;
import javafx.scene.paint.Color;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
public class Renderer {
private static final Logger logger = LogManager.getLogger(Renderer.class);
private TriangulatedGeometry currentTriGeom;
private Geometry currentGeometry;
private CullFace currentCulling = CullFace.BACK;
private DrawMode currentDrawMode = DrawMode.FILL;
private MainWindow mainWindow;
private HighlightController highlightController;
private ListErrorVisitor errVisitor;
private LoadingInfoDialog loadingDialog;
private List<ViewFilter> lodFilters = new ArrayList<>();
private Runnable refresher;
private Runnable errorUpdater;
public Renderer(MainWindow mainWindow, HighlightController highlightController) throws IOException {
this.mainWindow = mainWindow;
this.highlightController = highlightController;
loadingDialog = new LoadingInfoDialog(mainWindow.getMainStage());
errVisitor = new ListErrorVisitor(highlightController);
setupLodFilters();
}
private void setupLodFilters() {
lodFilters.add(new ViewFilter() {
@Override
public boolean useGeometry(CityObject co, Geometry geom) {
return geom.getLod() == Lod.LOD1;
}
});
lodFilters.add(new ViewFilter() {
@Override
public boolean useGeometry(CityObject co, Geometry geom) {
return geom.getLod() == Lod.LOD2;
}
});
lodFilters.add(new ViewFilter() {
@Override
public boolean useGeometry(CityObject co, Geometry geom) {
return geom.getLod() == Lod.LOD3;
}
});
lodFilters.add(new ViewFilter() {
@Override
protected boolean useGeometry(CityObject co, Geometry geom) {
return geom.getLod() == Lod.LOD0;
}
});
lodFilters.add(new ViewFilter() {
@Override
public boolean useGeometry(CityObject co, Geometry geom) {
return geom.getLod() == Lod.LOD4;
}
});
}
public void enableLod1() {
lodFilters.get(0).enable();
if (refresher != null) {
refresher.run();
}
}
public void disableLod1() {
lodFilters.get(0).disable();
if (refresher != null) {
refresher.run();
}
}
public void enableLod2() {
lodFilters.get(1).enable();
if (refresher != null) {
refresher.run();
}
}
public void disableLod2() {
lodFilters.get(1).disable();
if (refresher != null) {
refresher.run();
}
}
public void enableLod3() {
lodFilters.get(2).enable();
if (refresher != null) {
refresher.run();
}
}
public void disableLod3() {
lodFilters.get(2).disable();
if (refresher != null) {
refresher.run();
}
}
public void enableLod4() {
lodFilters.get(3).enable();
if (refresher != null) {
refresher.run();
}
}
public void disableLod4() {
lodFilters.get(3).disable();
if (refresher != null) {
refresher.run();
}
}
public void render(Building building) {
refresher = () -> {
Set<ConcretePolygon> setupBuildingPolygons = setupBuildingPolygons(building);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupBuildingPolygons));
render(setupBuildingPolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(building);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupBuildingPolygons(Building b) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(b, polygons);
for (BoundarySurface bs : b.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
}
for (Installation bi : b.getBuildingInstallations()) {
addPolygons(bi, polygons);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
}
}
for (BuildingPart bp : b.getBuildingParts()) {
polygons.addAll(setupBuildingPartPolygons(bp));
}
return polygons;
}
public void render(BuildingPart bp) {
refresher = () -> {
Set<ConcretePolygon> setupBuildingPartPolygons = setupBuildingPartPolygons(bp);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupBuildingPartPolygons));
render(setupBuildingPartPolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(bp);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupBuildingPartPolygons(BuildingPart bp) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(bp, polygons);
for (BoundarySurface bs : bp.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
}
for (Installation bi : bp.getBuildingInstallations()) {
addPolygons(bi, polygons);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
}
}
return polygons;
}
public void render(BridgeObject bridge) {
refresher = () -> {
Set<ConcretePolygon> setupBridgePolygons = setupBridgePolygons(bridge);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupBridgePolygons));
render(setupBridgePolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(bridge);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupBridgePolygons(BridgeObject bridge) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(bridge, polygons);
for (BoundarySurface bs : bridge.getBoundarySurfaces()) {
addPolygons(bs, polygons);
}
for (BridgeConstructiveElement consElement : bridge.getConstructiveElements()) {
addPolygons(consElement, polygons);
for (BoundarySurface bs : consElement.getBoundarySurfaces()) {
addPolygons(bs, polygons);
}
}
for (Installation inst : bridge.getBridgeInstallations()) {
addPolygons(inst, polygons);
for (BoundarySurface bs : inst.getBoundarySurfaces()) {
addPolygons(bs, polygons);
}
}
return polygons;
}
public void render(CityObject co) {
refresher = () -> {
Set<ConcretePolygon> setupCityObjectPolygons = setupCityObjectPolygons(co);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupCityObjectPolygons));
render(setupCityObjectPolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(co);
errorUpdater.run();
});
};
refresher.run();
}
public void render(ReliefObject relief) {
refresher = () -> {
Set<ConcretePolygon> setupCityObjectPolygons = setupReliefPolygons(relief);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupCityObjectPolygons));
render(setupCityObjectPolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(relief);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupReliefPolygons(ReliefObject relief) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(relief, polygons);
for (TinObject tin : relief.getComponents()) {
addPolygons(tin, polygons);
}
return polygons;
}
public void render(Installation bi) {
refresher = () -> {
Set<ConcretePolygon> setupCityObjectPolygons = setupBuildingInstallationPolygons(bi);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupCityObjectPolygons));
render(setupCityObjectPolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(bi);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupBuildingInstallationPolygons(Installation bi) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(bi, polygons);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
}
return polygons;
}
public void render(BoundarySurface bs) {
refresher = () -> {
Set<ConcretePolygon> setupBoundarySurfacePolygons = setupBoundarySurfacePolygons(bs);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(setupBoundarySurfacePolygons));
render(setupBoundarySurfacePolygons);
Platform.runLater(() -> {
errorUpdater = () -> displayErrors(bs);
errorUpdater.run();
});
};
refresher.run();
}
private Set<ConcretePolygon> setupBoundarySurfacePolygons(BoundarySurface bs) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(bs, polygons);
for (Opening op : bs.getOpenings()) {
addPolygons(op, polygons);
}
return polygons;
}
private Set<ConcretePolygon> setupCityObjectPolygons(CityObject co) {
Set<ConcretePolygon> polygons = new HashSet<>();
addPolygons(co, polygons);
return polygons;
}
private void addPolygons(CityObject co, Set<ConcretePolygon> polygons) {
for (Geometry geom : co.getGeometries()) {
boolean used = false;
for (ViewFilter filter : lodFilters) {
if (filter.allowedToUse(co, geom)) {
used = true;
break;
}
}
if (used) {
addConcretePolygons(polygons, geom);
}
}
}
private void displayErrors(Checkable c) {
if (!c.isValidated()) {
return;
}
List<CheckError> errors = new ArrayList<>();
c.collectContainedErrors(errors);
// filter out duplicate errors (polygon can be contained in multiple geometries)
Set<CheckError> errorSet = new HashSet<>(errors);
for (CheckError err : errorSet) {
ErrorNode node = new ErrorNode(err);
TreeItem<Renderable> errItem = new TreeItem<>(node);
ErrorItemVisitor visitor = new ErrorItemVisitor(errItem);
err.accept(visitor);
mainWindow.getErrorTree().getRoot().getChildren().add(errItem);
}
}
public void render(Geometry geom) {
refresher = () -> {
Platform.runLater(this::clearGeometryTrees);
currentTriGeom = TriangulatedGeometry.of(geom);
if (geom.getEdges() == null && currentGeometry != null) {
// if there are no edges available low memory mode is enabled
// clear the old geometry of all meta information
currentGeometry.clearMetaInformation();
}
errVisitor.setGeometry(currentTriGeom);
Platform.runLater(() -> {
setupRenderState();
if (geom.getEdges() == null) {
// create edges and vertices so they can be listed in the gui
geom.prepareForChecking();
// remember the geometry, so it can be cleared if another is displayed
currentGeometry = geom;
}
addGeometryDataToView(geom);
mainWindow.zoomOutForBoundingBox(BoundingBox.of(geom.getPolygons()));
errorUpdater = () -> displayErrors(geom);
errorUpdater.run();
});
};
refresher.run();
}
private void addGeometryDataToView(Geometry geom) {
for (Polygon p : geom.getPolygons()) {
addPolygonToView(p);
}
for (Edge e : geom.getEdges()) {
addEdgeToView(e);
}
for (Vertex v : geom.getVertices()) {
addVertexToView(v);
}
}
private void addVertexToView(Vertex v) {
Renderable cf = new VertexNode(v);
TreeItem<Renderable> ti = new TreeItem<>(cf);
mainWindow.getVertexView().getRoot().getChildren().add(ti);
}
private void addEdgeToView(Edge e) {
EdgeNode cf = new EdgeNode(e);
TreeItem<Renderable> ti = new TreeItem<>(cf);
mainWindow.getEdgeView().getRoot().getChildren().add(ti);
}
private void addPolygonToView(Polygon p) {
CheckStatus cs = determineCheckStatus(p);
PolygonNode cf = new PolygonNode(p, cs);
TreeItem<Renderable> ti = new TreeItem<>(cf);
mainWindow.getPolygonsView().getRoot().getChildren().add(ti);
// add linear rings
CheckStatus csRing = determineCheckStatus(p.getExteriorRing());
Renderable ccRing = new LinearRingNode(p.getExteriorRing(), csRing);
TreeItem<Renderable> tiRing = new TreeItem<>(ccRing);
ti.getChildren().add(tiRing);
for (Vertex v : p.getExteriorRing().getVertices()) {
VertexNode vn = new VertexNode(v);
TreeItem<Renderable> tiV = new TreeItem<>(vn);
tiRing.getChildren().add(tiV);
}
for (LinearRing lr : p.getInnerRings()) {
CheckStatus csInteriorRing = determineCheckStatus(lr);
Renderable ccInteriorRing = new LinearRingNode(lr, csInteriorRing);
TreeItem<Renderable> tiInteriorRing = new TreeItem<>(ccInteriorRing);
ti.getChildren().add(tiInteriorRing);
for (Vertex v : lr.getVertices()) {
VertexNode vn = new VertexNode(v);
TreeItem<Renderable> tiV = new TreeItem<>(vn);
tiRing.getChildren().add(tiV);
}
}
}
private CheckStatus determineCheckStatus(Checkable c) {
if (!c.isValidated()) {
return CheckStatus.NOT_CHECKED;
}
if (c.containsAnyError()) {
return CheckStatus.ERROR;
}
return CheckStatus.OK;
}
private void render(Collection<? extends Polygon> polygons) {
Platform.runLater(this::clearGeometryTrees);
currentTriGeom = TriangulatedGeometry.of(polygons);
errVisitor.setGeometry(currentTriGeom);
setupRenderState();
}
private void clearGeometryTrees() {
highlightController.clearHighlights();
mainWindow.getErrorTree().getRoot().getChildren().clear();
mainWindow.getPolygonsView().getRoot().getChildren().clear();
mainWindow.getEdgeView().getRoot().getChildren().clear();
mainWindow.getVertexView().getRoot().getChildren().clear();
}
public void renderBuildings(List<Building> objects) {
errorUpdater = null;
refresher = () -> {
Platform.runLater(() -> {
loadingDialog.show();
clearGeometryTrees();
});
Thread t = new Thread(() -> {
Set<ConcretePolygon> polygons = new HashSet<>();
for (Building b : objects) {
collectPolygons(polygons, b);
for (BuildingPart bp : b.getBuildingParts()) {
collectPolygons(polygons, bp);
}
}
currentTriGeom = TriangulatedGeometry.of(polygons);
errVisitor.setGeometry(currentTriGeom);
Platform.runLater(() -> {
setupRenderState();
mainWindow.zoomOutForBoundingBox(BoundingBox.of(polygons));
loadingDialog.hide();
});
});
t.setUncaughtExceptionHandler((thread, e) -> {
Platform.runLater(() -> loadingDialog.hide());
logger.catching(e);
});
t.start();
};
refresher.run();
}
public void render(CityDoctorModel model) {
errorUpdater = null;
refresher = () -> {
Platform.runLater(() -> {
loadingDialog.show();
clearGeometryTrees();
});
Thread t = new Thread(() -> {
currentTriGeom = TriangulatedGeometry.of(model, lodFilters);
errVisitor.setGeometry(currentTriGeom);
Platform.runLater(() -> {
setupRenderState();
mainWindow.zoomOutForBoundingBox(BoundingBox.of(model));
loadingDialog.hide();
});
});
t.setUncaughtExceptionHandler((thread, e) -> {
Platform.runLater(() -> loadingDialog.hide());
logger.catching(e);
});
t.start();
};
refresher.run();
}
public void renderVegetation(List<Vegetation> vegetation) {
renderCityObjects(vegetation, Color.LIGHTGREEN);
}
public void renderTransportation(List<TransportationObject> transportation) {
renderCityObjects(transportation, Color.YELLOW);
}
public void renderBridges(List<BridgeObject> bridges) {
renderCityObjects(bridges, Color.CORAL);
}
public void renderWater(List<WaterObject> water) {
renderCityObjects(water, Color.LIGHTSKYBLUE);
}
public void renderTerrain(List<CityObject> land) {
renderLandObjects(land, Color.BROWN);
}
private void renderLandObjects(List<CityObject> cos, Color baseColor) {
errorUpdater = null;
refresher = () -> {
Platform.runLater(() -> {
loadingDialog.show();
clearGeometryTrees();
});
Thread t = new Thread(() -> {
Set<ConcretePolygon> polygons = new HashSet<>();
for (CityObject co : cos) {
addPolygons(co, polygons);
if (co instanceof ReliefObject) {
ReliefObject relief = (ReliefObject) co;
for (TinObject tin : relief.getComponents()) {
addPolygons(tin, polygons);
}
}
}
currentTriGeom = TriangulatedGeometry.of(polygons, baseColor);
errVisitor.setGeometry(currentTriGeom);
Platform.runLater(() -> {
setupRenderState();
mainWindow.zoomOutForBoundingBox(BoundingBox.of(polygons));
loadingDialog.hide();
});
});
t.setUncaughtExceptionHandler((thread, e) -> Platform.runLater(() -> loadingDialog.hide()));
t.start();
};
refresher.run();
}
public void renderCityObjects(List<? extends CityObject> cos, Color baseColor) {
errorUpdater = null;
refresher = () -> {
Platform.runLater(() -> {
loadingDialog.show();
clearGeometryTrees();
});
Thread t = new Thread(() -> {
Set<ConcretePolygon> polygons = new HashSet<>();
for (CityObject co : cos) {
addPolygons(co, polygons);
}
currentTriGeom = TriangulatedGeometry.of(polygons, baseColor);
errVisitor.setGeometry(currentTriGeom);
Platform.runLater(() -> {
setupRenderState();
mainWindow.zoomOutForBoundingBox(BoundingBox.of(polygons));
loadingDialog.hide();
});
});
t.setUncaughtExceptionHandler((thread, e) -> Platform.runLater(() -> loadingDialog.hide()));
t.start();
};
refresher.run();
}
private void collectPolygons(Set<ConcretePolygon> polygons, AbstractBuilding ab) {
addPolygons(ab, polygons);
for (Installation bi : ab.getBuildingInstallations()) {
addPolygons(bi, polygons);
for (BoundarySurface bs : bi.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening o : bs.getOpenings()) {
addPolygons(o, polygons);
}
}
}
for (BoundarySurface bs : ab.getBoundarySurfaces()) {
addPolygons(bs, polygons);
for (Opening o : bs.getOpenings()) {
addPolygons(o, polygons);
}
}
}
private void addConcretePolygons(Set<ConcretePolygon> polygons, Geometry geom) {
for (Polygon p : geom.getPolygons()) {
polygons.add(p.getOriginal());
}
}
private void setupRenderState() {
currentTriGeom.setCullFace(currentCulling);
currentTriGeom.setDrawMode(currentDrawMode);
Platform.runLater(() -> {
mainWindow.getMeshGroup().getChildren().clear();
mainWindow.getMeshGroup().getChildren().addAll(currentTriGeom.getMeshes());
});
mainWindow.getGridButton().setDisable(false);
mainWindow.getCullingButton().setDisable(false);
}
public void showWireFrame(boolean show) {
if (currentTriGeom != null) {
if (show) {
currentDrawMode = DrawMode.LINE;
} else {
currentDrawMode = DrawMode.FILL;
}
currentTriGeom.setDrawMode(currentDrawMode);
}
}
public void enableCulling(boolean enable) {
if (currentTriGeom != null) {
if (enable) {
currentCulling = CullFace.BACK;
} else {
currentCulling = CullFace.NONE;
}
currentTriGeom.setCullFace(currentCulling);
}
}
public void clearCurrentRender() {
// don't render anything
Platform.runLater(() -> {
mainWindow.getMeshGroup().getChildren().clear();
clearGeometryTrees();
});
}
public void highlight(Polygon p) {
highlightController.highlight(p, currentTriGeom);
}
public void highlight(LinearRing lr) {
highlightController.highlight(lr, currentTriGeom);
}
public void highlight(Edge e) {
highlightController.highlight(e, currentTriGeom);
}
public void highlight(Vertex v) {
highlightController.highlight(v, currentTriGeom);
}
public void highlight(List<LinearRing> highlightedRings) {
highlightController.highlight(highlightedRings, currentTriGeom);
}
public void highlightEdges(List<Edge> edges) {
highlightController.highlightEdges(edges, currentTriGeom);
}
public void highlightPolygons(List<List<Polygon>> components) {
highlightController.highlightPolygons(components, currentTriGeom);
}
public void addHighlight(Vertex vertex, Color c) {
highlightController.addHighlight(vertex, currentTriGeom, c);
}
public void highlight(CheckError err) {
err.accept(errVisitor);
}
public void refresh() {
if (refresher != null) {
refresher.run();
}
}
public void updateErrors() {
if (errorUpdater != null) {
errorUpdater.run();
}
}
public void clearHighlights() {
highlightController.clearHighlights();
}
public void addHighlight(Polygon p) {
highlightController.addHighlight(p, currentTriGeom);
}
public void addHighlight(Triangle3d t) {
highlightController.highlight(t, currentTriGeom);
}
public void reset() {
errorUpdater = null;
refresher = null;
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Settings {
private static final Logger logger = LogManager.getLogger(Settings.class);
public static final String LAST_OPEN_FOLDER = "lastOpenFolder";
public static final String MAXIMIZED = "maximized";
public static final String FRAME_HEIGHT = "frameHeight";
public static final String FRAME_WIDTH = "frameWidth";
public static final String FRAME_X = "frameX";
public static final String FRAME_Y = "frameY";
public static final String LANGUAGE = "language";
private static Properties props;
static {
props = new Properties();
File propFile = new File("GUISettings.properties");
if (propFile.exists()) {
try (BufferedReader bis = new BufferedReader(new FileReader(propFile))) {
props.load(bis);
} catch (IOException e) {
logger.error("Failed to load settings", e);
}
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(propFile))) {
props.store(bw, "GUI configuration");
} catch (IOException e) {
logger.error("Failed to save settings", e);
}
}));
}
private Settings() {
}
public static String get(String name) {
return props.getProperty(name);
}
public static void set(String name, String value) {
props.setProperty(name, value);
}
public static String get(String name, String defaultV) {
return props.getProperty(name, defaultV);
}
}
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.gui;
import javafx.event.Event;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class TableEditCell<S, T> extends TableCell<S, T> {
// Text field for editing
private final TextField textField = new TextField();
// Converter for converting the text in the text field to the user type, and
// vice-versa:
private final StringConverter<T> converter;
public TableEditCell(StringConverter<T> converter) {
this.converter = converter;
itemProperty().addListener((obx, oldItem, newItem) -> {
if (newItem == null) {
setText(null);
} else {
setText(converter.toString(newItem));
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
textField.setOnAction(evt -> commitEdit(this.converter.fromString(textField.getText())));
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (Boolean.FALSE.equals(isNowFocused)) {
commitEdit(this.converter.fromString(textField.getText()));
}
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
textField.setText(converter.toString(getItem()));
cancelEdit();
event.consume();
} else if (event.getCode() == KeyCode.RIGHT) {
getTableView().getSelectionModel().selectRightCell();
event.consume();
} else if (event.getCode() == KeyCode.LEFT) {
getTableView().getSelectionModel().selectLeftCell();
event.consume();
} else if (event.getCode() == KeyCode.UP) {
getTableView().getSelectionModel().selectAboveCell();
event.consume();
} else if (event.getCode() == KeyCode.DOWN) {
getTableView().getSelectionModel().selectBelowCell();
event.consume();
}
});
}
/**
* Convenience converter that does nothing (converts Strings to themselves and
* vice-versa...).
*/
public static final StringConverter<String> IDENTITY_CONVERTER = new StringConverter<>() {
@Override
public String toString(String object) {
return object;
}
@Override
public String fromString(String string) {
return string;
}
};
/**
* Convenience method for creating an EditCell for a String value.
*
* @return
*/
public static <S> TableEditCell<S, String> createStringEditCell() {
return new TableEditCell<>(IDENTITY_CONVERTER);
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
textField.setText(converter.toString(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
// commits the edit. Update property if possible and revert to text display
@Override
public void commitEdit(T item) {
// This block is necessary to support commit on losing focus, because the
// baked-in mechanism
// sets our editing state to false before we can intercept the loss of focus.
// The default commitEdit(...) method simply bails if we are not editing...
if (!isEditing() && !item.equals(getItem())) {
TableView<S> table = getTableView();
if (table != null) {
TableColumn<S, T> column = getTableColumn();
CellEditEvent<S, T> event = new CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column), TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
\ No newline at end of file
/*-
* Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
*
* This file is part of CityDoctor2.
*
* CityDoctor2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CityDoctor2 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CityDoctor2. If not, see <https://www.gnu.org/licenses/>.
*/
package de.hft.stuttgart.citydoctor2.gui;
import javafx.event.Event;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellEditEvent;
import javafx.scene.control.TreeTablePosition;
import javafx.scene.control.TreeTableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class TreeEditCell<S, T> extends TreeTableCell<S, T> {
// Text field for editing
private final TextField textField = new TextField();
// Converter for converting the text in the text field to the user type, and
// vice-versa:
private final StringConverter<T> converter;
public TreeEditCell(StringConverter<T> converter) {
this.converter = converter;
itemProperty().addListener((obx, oldItem, newItem) -> {
if (newItem == null) {
setText(null);
} else {
setText(converter.toString(newItem));
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
textField.setOnAction(evt -> commitEdit(this.converter.fromString(textField.getText())));
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (Boolean.FALSE.equals(isNowFocused)) {
commitEdit(this.converter.fromString(textField.getText()));
}
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
textField.setText(converter.toString(getItem()));
cancelEdit();
event.consume();
} else if (event.getCode() == KeyCode.RIGHT) {
getTreeTableView().getSelectionModel().selectRightCell();
event.consume();
} else if (event.getCode() == KeyCode.LEFT) {
getTreeTableView().getSelectionModel().selectLeftCell();
event.consume();
} else if (event.getCode() == KeyCode.UP) {
getTreeTableView().getSelectionModel().selectAboveCell();
event.consume();
} else if (event.getCode() == KeyCode.DOWN) {
getTreeTableView().getSelectionModel().selectBelowCell();
event.consume();
}
});
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
textField.setText(converter.toString(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
// commits the edit. Update property if possible and revert to text display
@Override
public void commitEdit(T item) {
// This block is necessary to support commit on losing focus, because the
// baked-in mechanism
// sets our editing state to false before we can intercept the loss of focus.
// The default commitEdit(...) method simply bails if we are not editing...
if (!isEditing() && !item.equals(getItem())) {
TreeTableView<S> table = getTreeTableView();
if (table != null) {
TreeTableColumn<S, T> column = getTableColumn();
CellEditEvent<S, T> event = new CellEditEvent<>(table,
new TreeTablePosition<>(table, getIndex(), column), TreeTableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface;
import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType;
import de.hft.stuttgart.citydoctor2.datastructure.Building;
import de.hft.stuttgart.citydoctor2.datastructure.Installation;
import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
import de.hft.stuttgart.citydoctor2.datastructure.Opening;
import de.hft.stuttgart.citydoctor2.datastructure.OpeningType;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.gui.filter.ViewFilter;
import de.hft.stuttgart.citydoctor2.math.Triangle3d;
import de.hft.stuttgart.citydoctor2.math.UnitVector3d;
import de.hft.stuttgart.citydoctor2.math.Vector3d;
import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.shape.VertexFormat;
public class TriangulatedGeometry {
private static final PhongMaterial GRID_MAT = new PhongMaterial(Color.BLACK);
// random vector for calculating normal angles, for color determination
private static final UnitVector3d AXIS = new Vector3d(19, 0.8, 1.5).normalize();
private Vector3d movedBy;
private List<MeshView> meshes;
private List<PhongMaterial> materials;
public static TriangulatedGeometry of(Geometry geom) {
return of(geom.getPolygons());
}
public static TriangulatedGeometry of(Collection<? extends Polygon> polygons, Color basePolygonColor) {
TriangulatedGeometry triGeom = new TriangulatedGeometry();
triGeom.materials = new ArrayList<>();
triGeom.meshes = new ArrayList<>();
List<Vector3d> points = new ArrayList<>();
for (Polygon p : polygons) {
points.addAll(p.getExteriorRing().getVertices());
}
triGeom.movedBy = triGeom.findCenter(points);
addPolygonDataToTriGeom(polygons, basePolygonColor, triGeom);
return triGeom;
}
private static void addPolygonDataToTriGeom(Collection<? extends Polygon> polygons, Color basePolygonColor,
TriangulatedGeometry triGeom) {
for (Polygon p : polygons) {
TesselatedPolygon tp = p.tesselate();
TriangleMesh triMesh = new TriangleMesh(VertexFormat.POINT_TEXCOORD);
Map<Vector3d, Integer> indexMap = new HashMap<>();
List<Vector3d> vertices = new ArrayList<>();
int index = 0;
for (Triangle3d t : tp.getTriangles()) {
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP1());
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP2());
index = triGeom.filterDuplicates(triMesh, indexMap, vertices, index, t.getP3());
}
for (Vector3d point : vertices) {
float x = (float) (point.getX() - triGeom.movedBy.getX());
float y = (float) (point.getY() - triGeom.movedBy.getY());
float z = (float) (point.getZ() - triGeom.movedBy.getZ());
triMesh.getPoints().addAll(x, y, z);
}
triMesh.getTexCoords().addAll(0, 0);
MeshView view = new MeshView(triMesh);
view.setUserData(new PolygonClickDispatcher(p));
PhongMaterial mat = triGeom.calculateMaterial(p, basePolygonColor);
triGeom.materials.add(mat);
triGeom.meshes.add(view);
}
}
public static TriangulatedGeometry of(CityDoctorModel model, List<ViewFilter> filters) {
List<Vector3d> points = new ArrayList<>();
addPointsFromBuildings(model.getBuildings(), points);
addPointsFromCityObject(model.getBridges(), points);
addPointsFromCityObject(model.getLand(), points);
addPointsFromCityObject(model.getTransportation(), points);
addPointsFromCityObject(model.getVegetation(), points);
addPointsFromCityObject(model.getWater(), points);
TriangulatedGeometry triGeom = new TriangulatedGeometry();
triGeom.materials = new ArrayList<>();
triGeom.meshes = new ArrayList<>();
triGeom.movedBy = triGeom.findCenter(points);
addPolygonDataFromBuildings(model.getBuildings(), triGeom, filters);
addPolygonDataFromCityObjects(model.getBridges(), triGeom, Color.CORAL, filters);
addPolygonDataFromCityObjects(model.getLand(), triGeom, Color.BROWN, filters);
addPolygonDataFromCityObjects(model.getTransportation(), triGeom, Color.YELLOW, filters);
addPolygonDataFromCityObjects(model.getVegetation(), triGeom, Color.LIGHTGREEN, filters);
addPolygonDataFromCityObjects(model.getWater(), triGeom, Color.LIGHTSKYBLUE, filters);
return triGeom;
}
private static void addPolygonDataFromBuildings(List<Building> buildings, TriangulatedGeometry triGeom, List<ViewFilter> filters) {
for (Building b : buildings) {
addPolygonData(b, triGeom, Color.WHITE, filters);
addPolygonDataFromBoundarySurfaces(b.getBoundarySurfaces(), triGeom, filters);
addPolygonDataFromCityObjects(b.getBuildingInstallations(), triGeom, Color.WHITE, filters);
for (Installation bi : b.getBuildingInstallations()) {
addPolygonDataFromCityObjects(bi.getBoundarySurfaces(), triGeom, Color.WHITE, filters);
}
for (BuildingPart bp : b.getBuildingParts()) {
addPolygonData(bp, triGeom, Color.WHITE, filters);
addPolygonDataFromBoundarySurfaces(bp.getBoundarySurfaces(), triGeom, filters);
addPolygonDataFromCityObjects(bp.getBuildingInstallations(), triGeom, Color.WHITE, filters);
}
}
}
private static void addPolygonDataFromBoundarySurfaces(List<BoundarySurface> boundarySurfaces,
TriangulatedGeometry triGeom, List<ViewFilter> filters) {
for (BoundarySurface bs : boundarySurfaces) {
addPolygonData(bs, triGeom, Color.WHITE, filters);
for (Opening o : bs.getOpenings()) {
addPolygonData(o, triGeom, Color.WHITE, filters);
}
}
}
private static void addPolygonDataFromCityObjects(List<? extends CityObject> cos,
TriangulatedGeometry triGeom, Color color, List<ViewFilter> filters) {
for (CityObject co : cos) {
addPolygonData(co, triGeom, color, filters);
}
}
private static void addPolygonData(CityObject co, TriangulatedGeometry triGeom, Color color, List<ViewFilter> filters) {
for (Geometry geom : co.getGeometries()) {
if (isGeometryFiltered(co, geom, filters)) {
continue;
}
List<Polygon> polygons = new ArrayList<>();
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
polygons.add(p);
}
addPolygonDataToTriGeom(polygons, color, triGeom);
}
}
private static boolean isGeometryFiltered(CityObject co, Geometry geom, List<ViewFilter> filters) {
for (ViewFilter filter : filters) {
if (filter.allowedToUse(co, geom)) {
return false;
}
}
return true;
}
private static void addPointsFromBuildings(List<Building> buildings, List<Vector3d> points) {
for (Building b : buildings) {
addPoints(b, points);
for (BuildingPart bp : b.getBuildingParts()) {
addPoints(bp, points);
addPointsFromCityObject(bp.getBoundarySurfaces(), points);
addPointsFromCityObject(bp.getBuildingInstallations(), points);
}
addPointsFromCityObject(b.getBoundarySurfaces(), points);
addPointsFromCityObject(b.getBuildingInstallations(), points);
}
}
private static void addPointsFromCityObject(List<? extends CityObject> cos, List<Vector3d> points) {
for (CityObject co : cos) {
addPoints(co, points);
}
}
private static void addPoints(CityObject co, List<Vector3d> points) {
for (Geometry geom : co.getGeometries()) {
for (Polygon p : geom.getPolygons()) {
if (p.isLink()) {
continue;
}
points.addAll(p.getExteriorRing().getVertices());
}
}
}
public static TriangulatedGeometry of(Collection<? extends Polygon> polygons) {
return of(polygons, Color.WHITE);
}
private int filterDuplicates(TriangleMesh triMesh, Map<Vector3d, Integer> indexMap, List<Vector3d> vertices,
int index, Vector3d v) {
Integer vertexIndex = indexMap.get(v);
if (vertexIndex == null) {
indexMap.put(v, index);
vertices.add(v);
vertexIndex = index;
index++;
}
triMesh.getFaces().addAll(vertexIndex, 0);
return index;
}
private PhongMaterial calculateMaterial(Polygon p, Color baseColor) {
Vector3d normal = p.calculateNormalNormalized();
BoundarySurface bs = p.getPartOfSurface();
if (bs != null) {
if (bs.getType() == BoundarySurfaceType.ROOF) {
baseColor = Color.RED;
} else if (bs.getType() == BoundarySurfaceType.GROUND) {
baseColor = Color.KHAKI;
}
}
baseColor = determineColorDependingOnParentType(p, baseColor);
double cos = normal.dot(AXIS);
double acos = Math.acos(cos);
// normalize to range [0.3, 0.9]
acos = acos / Math.PI;
acos = acos * 0.6 + 0.3;
Color derivedColor = baseColor.deriveColor(0, 1.0, acos, 1.0);
return new PhongMaterial(derivedColor);
}
private Color determineColorDependingOnParentType(Polygon p, Color baseColor) {
p = p.getOriginal();
Polygon p1 = p.getLinkedFromPolygon();
baseColor = changeBaseColorIfPolygonHasOpeningParent(p1, baseColor);
baseColor = changeBaseColorIfPolygonHasOpeningParent(p, baseColor);
return baseColor;
}
private Color changeBaseColorIfPolygonHasOpeningParent(Polygon p, Color baseColor) {
if (p == null) {
return baseColor;
}
CityObject parent = p.getParent().getParent();
if (parent instanceof Opening op) {
if (op.getType() == OpeningType.DOOR) {
baseColor = Color.ORANGE;
} else {
baseColor = Color.TEAL;
}
}
return baseColor;
}
private Vector3d findCenter(List<Vector3d> points) {
double xMin = Double.MAX_VALUE;
double yMin = Double.MAX_VALUE;
double zMin = Double.MAX_VALUE;
double xMax = Double.NEGATIVE_INFINITY;
double yMax = Double.NEGATIVE_INFINITY;
double zMax = Double.NEGATIVE_INFINITY;
for (Vector3d point : points) {
if (point.getX() < xMin) {
xMin = point.getX();
}
if (point.getX() > xMax) {
xMax = point.getX();
}
if (point.getY() < yMin) {
yMin = point.getY();
}
if (point.getY() > yMax) {
yMax = point.getY();
}
if (point.getZ() < zMin) {
zMin = point.getZ();
}
if (point.getZ() > zMax) {
zMax = point.getZ();
}
}
// center
double x = (xMax - xMin) / 2 + xMin;
double y = (yMax - yMin) / 2 + yMin;
double z = (zMax - zMin) / 2 + zMin;
return new Vector3d(x, y, z);
}
public Vector3d getMovedBy() {
return movedBy;
}
public void setCullFace(CullFace currentCulling) {
for (MeshView mesh : meshes) {
mesh.setCullFace(currentCulling);
}
}
public void setDrawMode(DrawMode currentDrawMode) {
if (currentDrawMode == DrawMode.LINE) {
for (MeshView mesh : meshes) {
mesh.setDrawMode(currentDrawMode);
mesh.setMaterial(GRID_MAT);
}
} else if (currentDrawMode == DrawMode.FILL) {
for (int i = 0; i < meshes.size(); i++) {
MeshView mesh = meshes.get(i);
mesh.setDrawMode(currentDrawMode);
mesh.setMaterial(materials.get(i));
}
}
}
public List<MeshView> getMeshes() {
return meshes;
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
public class ValidationView extends View {
private static final Logger logger = LogManager.getLogger(ValidationView.class);
private Image viewLogo;
private MainWindow mainWindow;
private CityDoctorController controller;
public ValidationView(MainWindow mainWindow, CityDoctorController controller) {
this.mainWindow = mainWindow;
this.controller = controller;
try (InputStream inStream = MainWindow.class.getResourceAsStream("icons/error_stat32x32.png")) {
viewLogo = new Image(inStream);
} catch (IOException e) {
logger.catching(e);
}
}
@Override
public Node getMainScreen() {
return mainWindow.getMainContainer();
}
@Override
public Optional<HBox> getToolbar() {
return Optional.of(mainWindow.getMainToolbar().getToolBar());
}
@Override
public void onHide() {
Platform.runLater(() -> {
mainWindow.unselectEverything();
mainWindow.getMeshGroup().getChildren().clear();
mainWindow.clearHighlights();
mainWindow.getErrorTree().getRoot().getChildren().clear();
mainWindow.getPolygonsView().getRoot().getChildren().clear();
mainWindow.getVertexView().getRoot().getChildren().clear();
mainWindow.getEdgeView().getRoot().getChildren().clear();
});
}
@Override
public void onShow(CityDoctorModel model, Checker checker) {
if (model == null) {
return;
}
controller.buildTrees();
controller.updateFeatureTrees();
}
@Override
public Image getViewLogo() {
return viewLogo;
}
@Override
public void initializeView(MainWindow mainWindow) {
// already initialized
}
}
package de.hft.stuttgart.citydoctor2.gui;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import javafx.scene.input.MouseEvent;
public class VertexClickDispatcher implements ClickDispatcher {
private Vertex v;
public VertexClickDispatcher(Vertex v) {
this.v = v;
}
@Override
public void click(MouseEvent me, ClickHandler handler) {
handler.onVertexClick(v, me);
}
}
package de.hft.stuttgart.citydoctor2.gui;
import org.locationtech.proj4j.ProjCoordinate;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.gui.tree.Renderable;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class VertexClickHandler implements ClickHandler {
private TreeView<Renderable> errorView;
private Renderer renderer;
private Stage stage;
private ParserConfiguration config;
public VertexClickHandler(TreeView<Renderable> errorView, Renderer renderer, Stage stage) {
this.errorView = errorView;
this.renderer = renderer;
this.stage = stage;
}
public void setConfig(ParserConfiguration config) {
this.config = config;
}
@Override
public void onPolygonClick(Polygon p, MouseEvent me) {
if (me.getButton() == MouseButton.PRIMARY) {
errorView.getSelectionModel().clearSelection();
renderer.highlight(p);
} else if (me.getButton() == MouseButton.SECONDARY) {
MenuItem mi = new MenuItem(p.getGmlId().getGmlString());
ContextMenu cMenu = new ContextMenu(mi);
cMenu.show(stage, me.getScreenX(), me.getScreenY());
}
}
@Override
public void onVertexClick(Vertex v, MouseEvent me) {
if (me.getButton() == MouseButton.SECONDARY) {
MenuItem mi1 = new CustomMenuItem(new Label("Vertex"));
double x = v.getX();
double y = v.getY();
if (config.getOriginalTransform() != null) {
ProjCoordinate p1 = new ProjCoordinate();
ProjCoordinate p2 = new ProjCoordinate();
p1.x = v.getX();
p1.y = v.getY();
config.getOriginalTransform().transform(p1, p2);
x = p2.x;
y = p2.y;
}
MenuItem mi2 = new MenuItem("x = " + x);
MenuItem mi3 = new MenuItem("y = " + y);
MenuItem mi4 = new MenuItem("z = " + v.getZ());
ContextMenu cMenu = new ContextMenu(mi1, mi2, mi3, mi4);
cMenu.show(stage, me.getScreenX(), me.getScreenY());
}
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.util.Optional;
import de.hft.stuttgart.citydoctor2.check.Checker;
import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
public abstract class View {
private boolean firstTime = true;
public abstract Optional<HBox> getToolbar();
public abstract Node getMainScreen();
public abstract Image getViewLogo();
public abstract void initializeView(MainWindow mainWindow);
public void fireOnShowEvent(CityDoctorModel model, Checker checker, MainWindow mainWindow) {
if (firstTime) {
firstTime = false;
initializeView(mainWindow);
}
onShow(model, checker);
}
public abstract void onHide();
public abstract void onShow(CityDoctorModel model, Checker checker);
}
package de.hft.stuttgart.citydoctor2.gui;
import java.util.LinkedHashSet;
import java.util.Set;
public class ViewRegistration {
private static Set<View> registeredViews = new LinkedHashSet<>();
private static View currentActiveView;
private ViewRegistration() {
// only static use
}
public static void registerView(View v) {
registeredViews.add(v);
}
public static Set<View> getRegisteredViews() {
return registeredViews;
}
static void setCurrentActiveView(View v) {
currentActiveView = v;
}
public static View getCurrentActiveView() {
return currentActiveView;
}
}
package de.hft.stuttgart.citydoctor2.gui;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.hft.stuttgart.citydoctor2.utils.Localization;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class WriteReportDialog {
private static Logger logger = LogManager.getLogger(WriteReportDialog.class);
private Stage stage;
private CityDoctorController controller;
private MainWindow mainWindow;
@FXML
private CheckBox pdfCheckBox;
@FXML
private TextField pdfFileField;
@FXML
private Button selectPdfBtn;
@FXML
private CheckBox xmlCheckBox;
@FXML
private TextField xmlFileField;
@FXML
private Button selectXmlFile;
@FXML
private Button cancelBtn;
@FXML
private Button writeBtn;
@FXML
private BarChart<String, Number> barChart;
@FXML
private Button saveImageBtn;
@FXML
private Label errorStatisticsLabel;
public WriteReportDialog(Stage parent, CityDoctorController controller, MainWindow mainWindow) throws IOException {
FXMLLoader loader = new FXMLLoader(WriteReportDialog.class.getResource("WriteReportDialog.fxml"));
loader.setController(this);
VBox box = loader.load();
this.controller = controller;
this.mainWindow = mainWindow;
stage = new Stage();
stage.getIcons().add(new Image(MainWindow.class.getResourceAsStream("icons/CityDoctor-Logo-rot_klein.jpg")));
stage.setScene(new Scene(box));
stage.initOwner(parent);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Write Reports");
stage.setOnHidden(we -> barChart.getData().clear());
setMaximumWidthOfBarChartColumns();
}
private void setMaximumWidthOfBarChartColumns() {
double maxBarWidth = 40;
double minCategoryGap = 10;
// setting maximum width of columns in bar chart
// from https://stackoverflow.com/questions/27302875/set-bar-chart-column-width-size
stage.getScene().widthProperty().addListener((obs, n, n1) -> {
if (barChart.getData().isEmpty())
return;
setCategoryWidthWhenWindowIsBigger(maxBarWidth, n, n1);
if (n != null && (n1.doubleValue() < n.doubleValue()) && barChart.getCategoryGap() > minCategoryGap) {
double barWidth;
CategoryAxis xAxis = (CategoryAxis) barChart.getXAxis();
do {
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + barChart.getBarGap());
barWidth = Math.min(maxBarWidth,
(avilableBarSpace / barChart.getData().size()) - barChart.getBarGap());
avilableBarSpace = (barWidth + barChart.getBarGap()) * barChart.getData().size();
barChart.setCategoryGap(catSpace - avilableBarSpace - barChart.getBarGap());
} while (barWidth < maxBarWidth && barChart.getCategoryGap() > minCategoryGap);
}
});
}
private void setCategoryWidthWhenWindowIsBigger(double maxBarWidth, Number n, Number n1) {
if (n != null && (n1.doubleValue() > n.doubleValue())) {
double barWidth;
CategoryAxis xAxis = (CategoryAxis) barChart.getXAxis();
do {
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (barChart.getCategoryGap() + barChart.getBarGap());
barWidth = (avilableBarSpace / barChart.getData().size()) - barChart.getBarGap();
if (barWidth > maxBarWidth) {
avilableBarSpace = (maxBarWidth + barChart.getBarGap()) * barChart.getData().size();
barChart.setCategoryGap(catSpace - avilableBarSpace - barChart.getBarGap());
}
} while (barWidth > maxBarWidth);
}
}
public void initialize() {
writeBtn.setText(Localization.getText("WriteReportDialog.writeBtn"));
cancelBtn.setText(Localization.getText("WriteReportDialog.cancelBtn"));
errorStatisticsLabel.setText(Localization.getText("WriteReportDialog.errorStatisticsLabel"));
saveImageBtn.setText(Localization.getText("WriteReportDialog.saveImageBtn"));
selectPdfBtn.setText(Localization.getText("WriteReportDialog.selectPdfBtn"));
selectXmlFile.setText(Localization.getText("WriteReportDialog.selectXmlFile"));
barChart.getXAxis().setLabel(Localization.getText("WriteReportDialog.xAxisLabel"));
barChart.getYAxis().setLabel(Localization.getText("WriteReportDialog.yAxisLabel"));
// formatter to display only whole numbers
NumberAxis yAxis = (NumberAxis) barChart.getYAxis();
yAxis.setTickLabelFormatter(new StringConverter<>() {
@Override
public String toString(Number object) {
if (object.doubleValue() % 1 < 0.00000001) {
return String.valueOf(object.intValue());
} else {
return "";
}
}
@Override
public Number fromString(String string) {
return Integer.parseInt(string);
}
});
saveImageBtn.setOnAction(ae -> {
FileChooser chooser = new FileChooser();
chooser.setInitialDirectory(new File("."));
chooser.getExtensionFilters().add(new ExtensionFilter("PNG-Image", "*.png"));
String fName = controller.getFileName();
fName = fName.substring(0, fName.lastIndexOf('.'));
chooser.setInitialFileName(fName + "_errors.png");
File imageFile = chooser.showSaveDialog(stage);
if (imageFile != null) {
WritableImage snapshot = barChart.snapshot(new SnapshotParameters(), null);
try {
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", imageFile);
} catch (IOException e) {
logger.error("Failed to save image", e);
mainWindow.showExceptionDialog(e);
}
}
});
pdfCheckBox.selectedProperty().addListener((obs, oldV, newV) -> {
selectPdfBtn.setDisable(!newV);
pdfFileField.setDisable(!newV);
});
xmlCheckBox.selectedProperty().addListener((obs, oldV, newV) -> {
selectXmlFile.setDisable(!newV);
xmlFileField.setDisable(!newV);
});
selectPdfBtn.setOnAction(ae -> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Select PDF File");
chooser.getExtensionFilters().add(new ExtensionFilter("PDF-Files", ".pdf"));
File pdfFile = chooser.showSaveDialog(stage);
if (pdfFile != null) {
pdfFileField.setText(pdfFile.getAbsolutePath());
}
});
selectXmlFile.setOnAction(ae -> {
FileChooser chooser = new FileChooser();
chooser.setTitle("Select XML File");
chooser.getExtensionFilters().add(new ExtensionFilter("XML-Files", ".xml"));
File xmlFile = chooser.showSaveDialog(stage);
if (xmlFile != null) {
xmlFileField.setText(xmlFile.getAbsolutePath());
}
});
initWriteButton();
cancelBtn.setOnAction(ae -> stage.close());
}
private void initWriteButton() {
writeBtn.setOnAction(ae -> {
stage.setOnCloseRequest(Event::consume);
cancelBtn.setDisable(true);
writeBtn.setDisable(true);
Thread t = new Thread(() -> {
try {
if (pdfCheckBox.isSelected()) {
String file = pdfFileField.getText();
File pdfFile = new File(file);
File parentFile = pdfFile.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
controller.writePdfReport(pdfFile);
}
if (xmlCheckBox.isSelected()) {
String file = xmlFileField.getText();
File xmlFile = new File(file);
File parentFile = xmlFile.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
controller.writeXmlReport(xmlFile);
}
} finally {
Platform.runLater(() -> {
stage.setOnCloseRequest(null);
cancelBtn.setDisable(false);
writeBtn.setDisable(false);
stage.close();
});
}
});
t.start();
});
}
public void show() {
XYChart.Series<String, Number> series = controller.createErrorSeries();
barChart.getData().clear();
barChart.getData().add(series);
stage.show();
}
}
package de.hft.stuttgart.citydoctor2.gui.filter;
import de.hft.stuttgart.citydoctor2.datastructure.FeatureType;
public class TypeFilterSelection {
private final FeatureType type;
private final String name;
public TypeFilterSelection(FeatureType type, String name) {
this.type = type;
this.name = name;
}
public FeatureType getType() {
return type;
}
@Override
public String toString() {
return name;
}
}
package de.hft.stuttgart.citydoctor2.gui.filter;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.Geometry;
public abstract class ViewFilter {
private boolean enabled = true;
protected abstract boolean useGeometry(CityObject co, Geometry geom);
public boolean allowedToUse(CityObject co, Geometry geom) {
if (!enabled) {
return false;
}
return useGeometry(co, geom);
}
public boolean isEnabled() {
return enabled;
}
public void enable() {
enabled = true;
}
public void disable() {
enabled = false;
}
public void setEnable(boolean enabled) {
this.enabled = enabled;
}
}
package de.hft.stuttgart.citydoctor2.gui.logger;
import java.io.Serializable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import javafx.application.Platform;
import javafx.scene.control.TextArea;
@Plugin(name="GuiLogger", category="Core", elementType="appender", printObject=true)
public class GuiLogger extends AbstractAppender {
private static TextArea area;
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private static final int CAPACITY = 20000;
private final StringBuilder buffer = new StringBuilder(CAPACITY);
private Thread daemonLoggerThread;
private boolean dirty = false;
public static void setTextArea(TextArea area) {
GuiLogger.area = area;
}
@PluginFactory
public static GuiLogger createAppender(
@PluginAttribute("name") String name,
@PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginElement("Filter") final Filter filter) {
if (name == null) {
LOGGER.error("No name provided for GuiLogger");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
GuiLogger guiLogger = new GuiLogger(name, filter, layout, ignoreExceptions, new Property[0]);
guiLogger.daemonLoggerThread = new Thread(() -> {
while (!Thread.interrupted()) {
if (guiLogger.dirty) {
Platform.runLater(() -> {
// set text doesn't scroll
// workaround with clear -> append
area.clear();
area.appendText(guiLogger.buffer.toString());
});
guiLogger.dirty = false;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
guiLogger.daemonLoggerThread.setDaemon(true);
guiLogger.daemonLoggerThread.start();
return guiLogger;
}
protected GuiLogger(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions,
Property[] properties) {
super(name, filter, layout, ignoreExceptions, properties);
}
@Override
public void append(LogEvent event) {
if (area == null) {
return;
}
readLock.lock();
try {
String s = new String(getLayout().toByteArray(event));
int capacityLeft = CAPACITY - buffer.length();
if (capacityLeft < s.length()) {
int delete = s.length() - capacityLeft;
buffer.delete(0, delete - 1);
}
buffer.append(s);
dirty = true;
} finally {
readLock.unlock();
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment