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

it.tidalwave.role.ui.javafx.impl.DefaultJavaFXBinder Maven / Gradle / Ivy

/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - hg clone https://bitbucket.org/tidalwave/northernwind-src
 * %%
 * Copyright (C) 2011 - 2013 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.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.role.ui.javafx.impl;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.Callable;
import java.io.File;
import java.nio.file.Path;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.application.Platform;
import com.google.common.annotations.VisibleForTesting;
import it.tidalwave.util.AsException;
import it.tidalwave.util.ui.UserNotificationWithFeedback;
import it.tidalwave.role.Displayable;
import it.tidalwave.role.SimpleComposite;
import it.tidalwave.role.ui.BoundProperty;
import it.tidalwave.role.ui.PresentationModel;
import it.tidalwave.role.ui.Selectable;
import it.tidalwave.role.ui.UserAction;
import it.tidalwave.role.ui.javafx.JavaFXBinder;
import lombok.extern.slf4j.Slf4j;
import static javafx.collections.FXCollections.*;
import javafx.scene.effect.BoxBlur;
import javafx.scene.effect.Effect;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@Slf4j
public class DefaultJavaFXBinder implements JavaFXBinder
  {
    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @VisibleForTesting final Callback, TreeCell> treeCellFactory =
            new Callback, TreeCell>()
      {
        @Override @Nonnull
        public TreeCell call (final @Nonnull TreeView p)
          {
            final TextFieldTreeCell cell = new TextFieldTreeCell<>();
            cell.setConverter(new StringConverter()
              {
                @Override
                public String toString (final @Nonnull PresentationModel pm)
                  {
                    try
                      {
                        return pm.as(Displayable.class).getDisplayName();
                      }
                    catch (AsException e)
                      {
                        return pm.toString();
                      }
                  }

                @Override
                public PresentationModel fromString (final @Nonnull String string)
                  {
                    throw new UnsupportedOperationException("Not supported yet.");
                  }
              });

            return cell;
          }
      };

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @VisibleForTesting final ChangeListener> treeItemChangeListener =
            new ChangeListener>()
      {
        @Override
        public void changed (final @Nonnull ObservableValue> ov,
                             final @Nonnull TreeItem oldItem,
                             final @Nonnull TreeItem item)
          {
            try
              {
                item.getValue().as(Selectable.class).select();
              }
            catch (AsException e)
              {
                log.debug("No Selectable role for {}", item); // ok, do nothing
              }
          }
      };

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void bind (final @Nonnull Button button, final @Nonnull UserAction action)
      {
        assertIsFxApplicationThread();

//        button.disableProperty().not().bind(new PropertyAdapter<>(action.enabled())); // FIXME: not
        button.setOnAction(new EventHandler()
          {
            @Override
            public void handle (final @Nonnull ActionEvent event)
              {
                action.actionPerformed();
              }
          });
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void bind (final @Nonnull MenuItem menuItem, final @Nonnull UserAction action)
      {
        assertIsFxApplicationThread();

//        button.disableProperty().not().bind(new PropertyAdapter<>(action.enabled())); // FIXME: not
        menuItem.setOnAction(new EventHandler()
          {
            @Override
            public void handle (final @Nonnull ActionEvent event)
              {
                action.actionPerformed();
              }
          });
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void bind (final @Nonnull TableView tableView,
                      final @Nonnull PresentationModel pm)
      {
        assertIsFxApplicationThread();

        tableView.setItems(observableArrayList(pm.as(SimpleComposite.class).findChildren().results()));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void bind (final @Nonnull TreeView treeView,
                      final @Nonnull PresentationModel pm)
      {
        assertIsFxApplicationThread();

        treeView.setRoot(createTreeItem(pm));
        treeView.setCellFactory(treeCellFactory);
        treeView.getSelectionModel().selectedItemProperty().addListener(treeItemChangeListener);
     }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void bindColumn (final @Nonnull TableView tableView,
                            final @Nonnegative int columnIndex,
                            final @Nonnull String id)
      {
        assertIsFxApplicationThread();

        final ObservableList rawColumns = tableView.getColumns(); // FIXME
        final ObservableList> columns =
                (ObservableList>)rawColumns;
        columns.get(columnIndex).setId(id); // FIXME: is it correct to use Id?
        columns.get(columnIndex).setCellValueFactory(new RowAdapter());
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public  void bindBidirectionally (final @Nonnull Property property1, final @Nonnull BoundProperty property2)
      {
        assertIsFxApplicationThread();

        property1.bindBidirectional(new PropertyAdapter<>(property2));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void openFileChooserFor (final @Nonnull UserNotificationWithFeedback notification,
                                    final @Nonnull BoundProperty selectedFile,
                                    final @Nonnull Window window)
      {
        log.debug("openFileChooserFor({}, {}, {})", notification, selectedFile, window);
        assertIsFxApplicationThread();

        final FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle(notification.getCaption());
        fileChooser.setInitialDirectory(selectedFile.get().toFile());

        // It seems we need to take care of modality: https://javafx-jira.kenai.com/browse/RT-13949
        final File file = runWhileDisabling(window, new Callable()
          {
            @Override
            public File call()
              {
                return fileChooser.showOpenDialog(window);
              }
          });

        notifyFile(file, notification, selectedFile);
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void openDirectoryChooserFor (final @Nonnull UserNotificationWithFeedback notification,
                                         final @Nonnull BoundProperty selectedFolder,
                                         final @Nonnull Window window)
      {
        log.debug("openDirectoryChooserFor({}, {}, {})", notification, selectedFolder, window);
        assertIsFxApplicationThread();

        final DirectoryChooser directoryChooser = new DirectoryChooser();
        directoryChooser.setTitle(notification.getCaption());
        directoryChooser.setInitialDirectory(selectedFolder.get().toFile());

        // It seems we need to take care of modality: https://javafx-jira.kenai.com/browse/RT-13949
        final File file = runWhileDisabling(window, new Callable()
          {
            @Override
            public File call()
              {
                return directoryChooser.showDialog(window);
              }
          });

        notifyFile(file, notification, selectedFolder);
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    private TreeItem createTreeItem (final @Nonnull PresentationModel pm)
      {
        final TreeItem rootItem = new TreeItem<>(pm);

        addChildren(rootItem, pm);

        return rootItem;
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    // FIXME: add on demand, upon node expansion
    private void addChildren (final @Nonnull TreeItem parentItem,
                              final @Nonnull PresentationModel pm)
      {
        final SimpleComposite composite = pm.as(SimpleComposite.class);

        for (final PresentationModel childPm : composite.findChildren().results())
          {
            final TreeItem childItem = new TreeItem<>(childPm);
            addChildren(childItem, childPm);
            parentItem.getChildren().add(childItem);
          }
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private void notifyFile (final @Nullable File file,
                             final @Nonnull UserNotificationWithFeedback notification,
                             final @Nonnull BoundProperty selectedFile)
      {
        Platform.runLater(new Runnable()
          {
            @Override
            public void run()
              {
                try
                  {
                    if (file == null)
                      {
                        notification.cancel();
                      }
                    else
                      {
                        selectedFile.set(file.toPath());
                        notification.confirm();
                      }
                  }
                catch (Exception e)
                  {
                    log.warn("", e);
                  }
              }
          });
      }

    /*******************************************************************************************************************
     *
     * Runs the given (@link Callable} while disabling the given {@link Window}.
     *
     * @param  window    the {@code Window} to disable
     * @param  callable  the (@code Callable} to run
     * @return           the (@code Callable} result
     *
     ******************************************************************************************************************/
    private  T runWhileDisabling (final @Nonnull Window window, final @Nonnull Callable callable)
      {
        final Parent root = window.getScene().getRoot();
        final Effect effect = root.getEffect();
        final boolean disabled = root.isDisable();

        try
          {
            root.setDisable(true);
            root.setEffect(createDisablingEffect());
            return callable.call();
          }
        catch (Exception e)
          {
            throw new RuntimeException(e);
          }
        finally
          {
            root.setEffect(effect);
            root.setDisable(disabled);
          }
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private void assertIsFxApplicationThread()
      {
        if (!Platform.isFxApplicationThread())
          {
            throw new AssertionError("Must run in the JavaFX Application Thread");
          }
      }

    /*******************************************************************************************************************
     *
     * TODO: delegate to a provider
     *
     ******************************************************************************************************************/
    private BoxBlur createDisablingEffect()
      {
        final BoxBlur bb = new BoxBlur();
        bb.setWidth(5);
        bb.setHeight(5);
        bb.setIterations(3);
        return bb;
      }
  }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy