All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.io7m.laurel.gui.internal.LImporterView Maven / Gradle / Ivy

/*
 * Copyright © 2024 Mark Raynsford  https://www.io7m.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


package com.io7m.laurel.gui.internal;

import com.io7m.jmulticlose.core.CloseableCollection;
import com.io7m.jmulticlose.core.CloseableCollectionType;
import com.io7m.jmulticlose.core.ClosingResourceFailedException;
import com.io7m.jwheatsheaf.api.JWFileChooserAction;
import com.io7m.jwheatsheaf.api.JWFileChooserConfiguration;
import com.io7m.laurel.filemodel.LFileModelEvent;
import com.io7m.laurel.filemodel.LFileModelEventError;
import com.io7m.laurel.filemodel.LFileModelEventType;
import com.io7m.laurel.filemodel.LFileModels;
import com.io7m.repetoir.core.RPServiceDirectoryType;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.io7m.laurel.gui.internal.LStringConstants.TITLE;

/**
 * The importer view.
 */

public final class LImporterView implements LViewType
{
  private static final Logger LOG =
    LoggerFactory.getLogger(LImporterView.class);

  private final LFileChoosersType fileChoosers;
  private final Stage stage;
  private final CloseableCollectionType resources;
  private final AtomicBoolean running;
  private final RPServiceDirectoryType services;

  @FXML private Button select;
  @FXML private Button create;
  @FXML private Button importButton;
  @FXML private TextField directoryField;
  @FXML private TextField fileField;
  @FXML private TextArea exceptionArea;
  @FXML private ProgressBar progress;
  @FXML private TableView> attributeTable;
  @FXML private TableColumn, String> attributeName;
  @FXML private TableColumn, String> attributeValue;
  @FXML private ListView eventList;

  /**
   * The importer view.
   *
   * @param inServices The services
   * @param inStage    The stage
   */

  public LImporterView(
    final RPServiceDirectoryType inServices,
    final Stage inStage)
  {
    this.services =
      Objects.requireNonNull(inServices, "services");
    this.fileChoosers =
      inServices.requireService(LFileChoosersType.class);
    this.stage =
      Objects.requireNonNull(inStage, "stage");
    this.resources =
      CloseableCollection.create();
    this.running =
      new AtomicBoolean(false);
  }

  @Override
  public void initialize(
    final URL url,
    final ResourceBundle resourceBundle)
  {
    this.importButton.setDisable(true);

    this.directoryField.textProperty()
      .addListener((_0, _1, _2) -> this.validate());
    this.fileField.textProperty()
      .addListener((_0, _1, _2) -> this.validate());

    this.attributeTable.setPlaceholder(new Label(""));
    this.attributeName.setCellValueFactory(param -> {
      return new ReadOnlyStringWrapper(param.getValue().getKey());
    });
    this.attributeValue.setCellValueFactory(param -> {
      return new ReadOnlyStringWrapper(param.getValue().getValue());
    });

    this.eventList.setCellFactory(v -> new LEventCell(this.services));
    this.eventList.getSelectionModel()
      .selectedItemProperty()
      .addListener((_0, _1, item) -> {
        this.onEventSelectionChanged(item);
      });

    this.stage.setOnHidden(windowEvent -> this.close());
  }

  private void onEventSelectionChanged(
    final LFileModelEventType item)
  {
    switch (item) {
      case null -> {
        this.attributeTable.setItems(FXCollections.emptyObservableList());
        this.exceptionArea.setText("");
      }
      case final LFileModelEvent ignored -> {
        this.attributeTable.setItems(FXCollections.emptyObservableList());
        this.exceptionArea.setText("");
      }
      case final LFileModelEventError error -> {
        this.attributeTable.setItems(
          FXCollections.observableList(
            List.copyOf(error.attributes().entrySet())
          )
        );
        this.exceptionArea.setText(exceptionTextOf(error.exception()));
      }
    }
  }

  private static String exceptionTextOf(
    final Optional exceptionOpt)
  {
    if (exceptionOpt.isEmpty()) {
      return "";
    }

    final var exception = exceptionOpt.get();
    try (var writer = new StringWriter()) {
      try (var printWriter = new PrintWriter(writer)) {
        exception.printStackTrace(printWriter);
        printWriter.flush();
      }
      return writer.toString();
    } catch (final IOException e) {
      throw new IllegalStateException(e);
    }
  }

  private void close()
  {
    try {
      this.resources.close();
    } catch (final ClosingResourceFailedException e) {
      // Nothing we can do!
    }
  }

  private void validate()
  {
    if (this.running.get()) {
      this.create.setDisable(true);
      this.directoryField.setDisable(true);
      this.fileField.setDisable(true);
      this.importButton.setDisable(true);
      this.select.setDisable(true);
      return;
    }

    var ok = !this.fileField.getText().isBlank();
    ok = ok && !this.directoryField.getText().isBlank();
    this.importButton.setDisable(!ok);
    this.create.setDisable(false);
    this.directoryField.setDisable(false);
    this.importButton.setDisable(false);
    this.select.setDisable(false);
  }

  @FXML
  private void onSelectDirectorySelected()
  {
    final var chooser =
      this.fileChoosers.create(
        JWFileChooserConfiguration.builder()
          .setAction(JWFileChooserAction.OPEN_EXISTING_SINGLE)
          .build()
      );

    final var r = chooser.showAndWait();
    if (!r.isEmpty()) {
      this.directoryField.setText(r.get(0).toAbsolutePath().toString());
    }
  }

  @FXML
  private void onCreateFileSelected()
  {
    final var chooser =
      this.fileChoosers.create(
        JWFileChooserConfiguration.builder()
          .setAction(JWFileChooserAction.CREATE)
          .setConfirmFileSelection(true)
          .build()
      );

    final var r = chooser.showAndWait();
    if (!r.isEmpty()) {
      this.fileField.setText(r.get(0).toAbsolutePath().toString());
    }
  }

  @FXML
  private void onImportSelected()
  {
    final var outputFile =
      Paths.get(this.fileField.getText());
    final var sourceDirectory =
      Paths.get(this.directoryField.getText());
    final var importer =
      this.resources.add(LFileModels.createImport(sourceDirectory, outputFile));

    importer.events().subscribe(new LPerpetualSubscriber<>(this::onFileModelEvent));

    final var future = importer.execute();
    future.whenComplete((unused, throwable) -> {
      Platform.runLater(() -> {
        if (throwable == null) {
          try {
            this.onImportCompleted(outputFile);
          } catch (final Exception e) {
            LOG.debug("Failed to open new file: ", e);
          }
        }
      });
    });

    this.running.set(true);
    this.validate();
  }

  private void onImportCompleted(
    final Path outputFile)
    throws Exception
  {
    final var newStage = new Stage();
    LFileView.openForStage(
      this.services,
      LFileModelScope.createNewScope(outputFile),
      newStage
    );
    newStage.show();

    this.stage.toFront();
  }

  private void onFileModelEvent(
    final LFileModelEventType event)
  {
    Platform.runLater(() -> {
      final var progressV = event.progress();
      if (progressV.isEmpty()) {
        this.progress.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
      } else {
        this.progress.setProgress(progressV.getAsDouble());
      }
      this.eventList.getItems().add(event);
    });
  }

  @FXML
  private void onCancelSelected()
  {
    this.stage.close();
  }

  /**
   * Open a new view for the given stage.
   *
   * @param services The service directory
   * @param stage    The stage
   *
   * @return A view and stage
   *
   * @throws Exception On errors
   */

  public static LViewAndStage openForStage(
    final RPServiceDirectoryType services,
    final Stage stage)
    throws Exception
  {
    final var strings =
      services.requireService(LStrings.class);

    final var xml =
      LFileView.class.getResource(
        "/com/io7m/laurel/gui/internal/importer.fxml"
      );
    final var resources =
      strings.resources();
    final var loader =
      new FXMLLoader(xml, resources);

    final LViewControllerFactoryType controllers =
      LViewControllerFactoryMapped.create(
        Map.entry(
          LImporterView.class,
          () -> {
            return new LImporterView(services, stage);
          }
        )
      );

    loader.setControllerFactory(param -> {
      return controllers.call((Class) param);
    });

    final var pane = loader.load();
    LCSS.setCSS(pane);
    stage.setScene(new Scene(pane));
    stage.setTitle(strings.format(TITLE));
    stage.setWidth(600.0);
    stage.setHeight(400.0);

    return new LViewAndStage<>(loader.getController(), stage);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy