JavaFxAudioExplorerPresentationDelegate.java

/*
 * *********************************************************************************************************************
 *
 * blueMarine II: Semantic Media Centre
 * http://tidalwave.it/projects/bluemarine2
 *
 * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * *********************************************************************************************************************
 *
 * git clone https://bitbucket.org/tidalwave/bluemarine2-src
 * git clone https://github.com/tidalwave-it/bluemarine2-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.bluemarine2.ui.audio.explorer.impl.javafx;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Optional;
import java.util.stream.Stream;
import java.net.URI;
import javafx.fxml.FXML;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import it.tidalwave.role.ui.PresentationModel;
import it.tidalwave.role.ui.UserAction;
import it.tidalwave.role.ui.javafx.JavaFXBinder;
import it.tidalwave.bluemarine2.ui.audio.explorer.AudioExplorerPresentation;
import it.tidalwave.bluemarine2.ui.audio.renderer.AudioRendererPresentation;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import static java.util.stream.Collectors.*;
import static it.tidalwave.bluemarine2.ui.impl.javafx.NodeFactory.*;

/***********************************************************************************************************************
 *
 * The JavaFX Delegate for {@link AudioRendererPresentation}.
 *
 * @stereotype  JavaFXDelegate
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
@Slf4j
public class JavaFxAudioExplorerPresentationDelegate implements AudioExplorerPresentation
  {
    @Getter @ToString
    static class Memento
      {
        private final int selectedIndex;

        public Memento()
          {
            selectedIndex = 0;
          }

        public Memento (@Nonnull final ListView<PresentationModel> lvFiles)
          {
        // TODO: add further properties, such as the precise scroller position
            selectedIndex = lvFiles.getSelectionModel().getSelectedIndex();
          }

        public void applyTo (@Nonnull final ListView<PresentationModel> lvFiles)
          {
            lvFiles.getSelectionModel().select(selectedIndex);
            lvFiles.scrollTo(selectedIndex);
          }
      }

    @FXML
    private ListView<PresentationModel> lvFiles;

    @FXML
    private Button btUp;

    @FXML
    private Label lbFolderName;

    @FXML
    private HBox hbBrowserButtons;

    @FXML
    private Pane pnCoverArt;

    @FXML
    private ImageView ivCoverArt;

    @FXML
    private VBox vbDetails;

    @Inject
    private JavaFXBinder binder;

    @FXML
    private void initialize()
      {
        pnCoverArt.prefHeightProperty().bind(pnCoverArt.widthProperty());
        ivCoverArt.fitWidthProperty().bind(pnCoverArt.widthProperty().multiply(0.9));
        ivCoverArt.fitHeightProperty().bind(pnCoverArt.heightProperty().multiply(0.9));
      }

    @Override
    public void bind (@Nonnull final Properties properties, @Nonnull final UserAction upAction)
      {
        binder.bind(btUp, upAction);
        lbFolderName.textProperty().bind(properties.folderNameProperty());
      }

    @Override
    public void showUp (@Nonnull final Object control)
      {
      }

    @Override
    public void populateBrowsers (@Nonnull final PresentationModel pm)
      {
        binder.bindToggleButtons(hbBrowserButtons, pm);
      }

    @Override
    public void populateItems (@Nonnull final PresentationModel pm, @Nonnull final Optional<Object> optionalMemento)
      {
        binder.bind(lvFiles, pm, () ->
          {
            if (!lvFiles.getItems().isEmpty())
              {
                ((Memento)optionalMemento.orElse(new Memento())).applyTo(lvFiles);
                lvFiles.requestFocus();
              }
          });
      }

    @Override
    public void renderDetails (@Nonnull final String entityDetails)
      {
        vbDetails.getChildren().setAll(Stream.of(entityDetails.split("\n"))
                                             .map(s -> label("track-details", s))
                                             .collect(toList()));
      }

    @Override
    public void focusOnMediaItems()
      {
        lvFiles.requestFocus();
      }

    @Override @Nonnull
    public Object getMemento()
      {
        return new Memento(lvFiles);
      }

    @Override
    public void setCoverArt (@Nonnull final Optional<URI> optionalCoverArtUri)
      {
        ivCoverArt.setImage(optionalCoverArtUri.map(uri -> new Image(uri.toString())).orElse(null));
      }

    /*******************************************************************************************************************
     *
     * With a remote there's no TAB key, so we must emulate some tab control with left and right arrows.
     *
     * FIXME: try to generalise (e.g. cyclefocus?) and move to JavaFxApplicationPresentationDelegate.
     *
     * @param   event   the key event
     *
     ******************************************************************************************************************/
    @FXML
    public void onKeyPressed (@Nonnull final KeyEvent event)
      {
        log.debug("onKeyPressed({})", event);

        if (lvFiles.isFocused())
          {
            if (event.getCode().equals(KeyCode.LEFT))
              {
                btUp.requestFocus();
              }
            else if (event.getCode().equals(KeyCode.RIGHT))
              {
                hbBrowserButtons.getChildren().get(0).requestFocus();
              }
          }
      }
  }