Package org.ugate.gui.view

Source Code of org.ugate.gui.view.RemoteNodes$NodeStatusView

package org.ugate.gui.view;

import java.util.HashMap;
import java.util.Map;

import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.control.ToolBar;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Modality;
import javafx.util.Callback;
import javafx.util.Duration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ugate.UGateEvent;
import org.ugate.UGateKeeper;
import org.ugate.UGateListener;
import org.ugate.gui.ControlBar;
import org.ugate.gui.GuiUtil;
import org.ugate.gui.components.FunctionButton;
import org.ugate.gui.components.StatusIcon;
import org.ugate.resources.RS;
import org.ugate.resources.RS.KEY;
import org.ugate.service.ServiceProvider;
import org.ugate.service.entity.ActorType;
import org.ugate.service.entity.Command;
import org.ugate.service.entity.RemoteNodeType;
import org.ugate.service.entity.jpa.RemoteNode;
import org.ugate.wireless.data.RxTxRemoteNodeDTO;

/**
* {@linkplain ToolBar} for displaying the status of each of the {@linkplain RemoteNode}s
*/
public class RemoteNodes extends ToolBar {

  private static final Logger log = LoggerFactory.getLogger(RemoteNodes.class);
  protected final ControlBar controlBar;
  private final TextField textField = new TextField();
  private final ListView<String> rnListView = new ListView<>();
 
  /**
   * Constructor
   *
   * @param controlBar the control bar
   * @param orientation the {@linkplain Orientation}
   */
  public RemoteNodes(final ControlBar controlBar, final Orientation orientation) {
    super();
    getStyleClass().add("dialog-base");
    setOrientation(orientation);
    this.controlBar = controlBar;
    this.rnListView.setOrientation(orientation);
    addGlobalNodeControls();
    registerListeners();
  }

  /**
   * Registers any {@linkplain UGateListener}s
   */
  protected final void registerListeners() {
    UGateKeeper.DEFAULT.addListener(new UGateListener() {
      @Override
      public void handle(final UGateEvent<?, ?> event) {
        final boolean isRemoteCommand = event.isFromRemote() && event.getCommand() != null;
        if (event.getType() == UGateEvent.Type.WIRELESS_REMOTE_NODE_COMMITTED) {
          // notify the user that the remote node has successfully been committed locally
          final RemoteNode rn = (RemoteNode) event.getSource();
          final NodeStatusView nsv = getNodeStatusView(rn.getAddress());
          if (isRemoteCommand && nsv != null) {
            // blink status to indicate the a remote command has been received
            nsv.updateLastCommand(event.getCommand(), false);
          }
          if (event.getNewValue() == null) {
            // remove from display
            NodeStatusView.remove(rn.getAddress());
            selectFromChange(null, true);
            log.info("Removed remote node at address: " + rn.getAddress());
          }
        } else if (event.getType() == UGateEvent.Type.WIRELESS_DATA_RX_SUCCESS) {
          final RemoteNode rn = (RemoteNode) event.getSource();
          if (event.getNewValue() instanceof RxTxRemoteNodeDTO) {
            final RxTxRemoteNodeDTO ndto = (RxTxRemoteNodeDTO) event.getNewValue();
            if (!RemoteNodeType.remoteEquivalent(rn, ndto.getRemoteNode())) {
              // remote device values do not match the local device values
              rn.setDeviceSynchronized(false);
              ndto.getRemoteNode().setDeviceSynchronized(false);
              final NodeStatusView nsv = getNodeStatusView(rn.getAddress());
              if (nsv != null) {
                // blink status to indicate the a remote device is out of sync
                nsv.updateLastCommand(event.getCommand(), true);
                controlBar.setHelpText(RS.rbLabel(
                    KEY.WIRELESS_NODE_REMOTE_SAVED_LOCAL,
                    ((RemoteNode) event.getSource()).getAddress()));
              }
            }
          }
        }
      }
    });
  }

  /**
   * Adds the user interaction controls for adding/removing nodes
   */
  protected void addGlobalNodeControls() {
    textField.setPromptText(RS.rbLabel(KEY.WIRELESS_NODE_REMOTE_PROMPT));
    // textField.setTooltip(new
    // Tooltip(RS.rbLabel("wireless.node.remote")));
    textField.setMaxWidth(100d);
    controlBar.addHelpTextTrigger(textField,
        RS.rbLabel(KEY.WIRELESS_NODE_REMOTE_ADDY_DESC));
    final Button addNodeButton = new FunctionButton(
        FunctionButton.Function.ADD, new Runnable() {
          @Override
          public void run() {
            add(textField.getText());
          }
        });
    controlBar.addHelpTextTrigger(addNodeButton,
        RS.rbLabel(KEY.WIRELESS_NODE_REMOTE_ADD_DESC));
    final Button removeNodeButton = new FunctionButton(
        FunctionButton.Function.REMOVE, new Runnable() {
          @Override
          public void run() {
            removeSelected();
          }
        });
    controlBar.addHelpTextTrigger(removeNodeButton,
        RS.rbLabel(KEY.WIRELESS_NODE_REMOTE_REMOVE_DESC));

    rnListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
            @Override
            public ListCell<String> call(final ListView<String> list) {
                return new NodeStatusView(controlBar);
            }
        });
    HBox.setHgrow(rnListView, Priority.ALWAYS);
    //VBox.setVgrow(rnListView, Priority.ALWAYS);
    rnListView.getStyleClass().add("remote-node-listview");
    rnListView.setPrefWidth(USE_PREF_SIZE);
    rnListView.setPrefHeight(30d);
    rnListView.getSelectionModel().selectedItemProperty()
        .addListener(new ChangeListener<String>() {
          @Override
          public void changed(
              final ObservableValue<? extends String> ov,
              final String oldAddress, final String newAddress) {
            selectFromChange(newAddress, true);
          }
        });
    controlBar.getActorPA().bindContentBidirectional(
        ActorType.REMOTE_NODES.getKey(),
        RemoteNodeType.WIRELESS_ADDRESS.getKey(), RemoteNode.class,
        rnListView.getItems(), String.class, null, null);
    getItems().addAll(textField, addNodeButton, removeNodeButton,
        new Separator(Orientation.VERTICAL), rnListView);
    selectFromChange(null, false);
  }

  /**
   * Selects a {@linkplain RemoteNode#getAddress()}
   *
   * @param address
   *            the {@linkplain RemoteNode#getAddress()} to select
   * @param dispatch
   *            true to dispatch a
   *            {@linkplain UGateEvent.Type#WIRELESS_REMOTE_NODE_CHANGED}
   * @return true if selection is successful
   */
  private boolean selectFromChange(final String address, final boolean dispatch) {
    if (address == null || address.isEmpty()) {
      final String currSel = rnListView.getSelectionModel().getSelectedItem();
      if (currSel != null
          && currSel.equalsIgnoreCase(controlBar.getRemoteNode()
              .getAddress())) {
        return false;
      }
      rnListView.getSelectionModel().select(
          controlBar.getRemoteNode().getAddress());
      if (dispatch) {
        notifySelectionChange(controlBar.getRemoteNode());
      }
      return true;
    } else if (controlBar.getRemoteNode().getAddress()
        .equalsIgnoreCase(address)) {
      return true;
    }
    for (final RemoteNode rn : controlBar.getActor().getHost()
        .getRemoteNodes()) {
      if (rn.getAddress().equalsIgnoreCase(address)) {
        controlBar.getRemoteNodePA().setBean(rn);
        if (dispatch) {
          notifySelectionChange(rn);
        }
        return true;
      }
    }
    return false;
  }

  /**
   * Notifies listeners of a
   * {@linkplain UGateEvent.Type#WIRELESS_REMOTE_NODE_CHANGED}
   *
   * @param remoteNode
   *            the {@linkplain RemoteNode} that was changed
   */
  private void notifySelectionChange(final RemoteNode remoteNode) {
    Platform.runLater(new Runnable() {
      @Override
      public void run() {
        UGateKeeper.DEFAULT
            .notifyListeners(new UGateEvent<RemoteNode, RemoteNode>(
                remoteNode,
                UGateEvent.Type.WIRELESS_REMOTE_NODE_CHANGED,
                false, null, null, remoteNode, controlBar
                    .getRemoteNode()));
      }
    });
  }

  /**
   * Adds a {@linkplain RemoteNode} based upon the given
   * {@linkplain RemoteNode#getAddress()}. The currently selected
   * {@linkplain RemoteNode} values will be used to initialize the new
   * {@linkplain RemoteNode}
   *
   * @param address
   *            the {@linkplain RemoteNode#getAddress()} to add
   */
  public void add(final String address) {
    if (address == null || address.isEmpty()) {
      return;
    }
    log.info("Attempting to add remote node at address: " + address);
    // node may have been added, but the save failed
    RemoteNode rnm = null;
    for (final RemoteNode rn : controlBar.getActor().getHost().getRemoteNodes()) {
      if (rn.getAddress().equalsIgnoreCase(address)) {
        rnm = rn;
        break;
      }
    }
    if (rnm == null) {
      final RemoteNode rn = RemoteNodeType.newDefaultRemoteNode(controlBar
          .getActor().getHost(), controlBar.getRemoteNode());
      rn.setAddress(address);
      controlBar.getActor().getHost().getRemoteNodes().add(rn);
      ServiceProvider.IMPL.getCredentialService().mergeHost(
          controlBar.getActor().getHost());
    }
  }

  /**
   * Removes the selected {@linkplain RemoteNode}
   */
  public void removeSelected() {
    final String address = rnListView.getSelectionModel().getSelectedItem();
    if (address == null || address.isEmpty()) {
      return;
    }
    final Button closeBtn = ButtonBuilder.create().text(RS.rbLabel(KEY.CLOSE)).build();
    final GuiUtil.DialogService dialogService = GuiUtil.dialogService(null, KEY.APP_TITLE,
        RS.rbLabel(KEY.WIRELESS_NODE_REMOTE_REMOVE, address),
        KEY.SUBMIT, 550d, 300d, new Service<Void>() {
      @Override
      protected Task<Void> createTask() {
        return new Task<Void>() {
          @Override
          protected Void call() throws Exception {
            // if the dialog shouldn't be closed call super.cancel()
            try {
              log.info("Attempting to remove remote node at address: "
                  + address);
              if (controlBar.getActor().getHost().getRemoteNodes().size() > 1) {
                RemoteNode rnr = null;
                for (final RemoteNode rn : controlBar.getActor().getHost().getRemoteNodes()) {
                  if (address.equalsIgnoreCase(rn.getAddress())) {
                    rnr = rn;
                    break;
                  }
                }
                if (rnr != null) {
                  // remove node and persist changes (actual removal from view will
                  // occur when the removal has been committed)
                  controlBar.getActor().getHost().getRemoteNodes().remove(rnr);
                  ServiceProvider.IMPL.getCredentialService().mergeHost(
                      controlBar.getActor().getHost(), rnr);
                }
              }
            } catch (final Throwable t) {
              throw new RuntimeException();
            }
            return null;
          }
        };
      }
    }, Modality.APPLICATION_MODAL, closeBtn);
    if (closeBtn != null) {
      closeBtn.setOnMouseClicked(new EventHandler<MouseEvent>() {
        @Override
        public void handle(final MouseEvent event) {
          dialogService.hide();
        }
      })
    }
    dialogService.start();
  }

  /**
   * Gets a {@linkplain NodeStatusView} for a specified
   * {@linkplain RemoteNode#getAddress()}
   *
   * @param address
   *            the {@linkplain RemoteNode#getAddress()}
   * @return the {@linkplain NodeStatusView}
   */
  public NodeStatusView getNodeStatusView(final String address) {
    if (address == null || address.isEmpty()) {
      return null;
    }
    return NodeStatusView.get(address);
  }

  /**
   * Node status view
   */
  protected static final class NodeStatusView extends ListCell<String> {

    // TODO : there is no public API to access the ListCell from a ListView
    // so ALL of them are maintained globally
    private static final Map<String, NodeStatusView> ALL = new HashMap<>();
    private final ControlBar controlBar;
    private String address;
    private final StatusIcon statusIcon;
    private final StringProperty helpTextStringProperty;
    private boolean isSet;
   
    /**
     * Constructor
     *
     * @param controlBar
     *            the {@linkplain ControlBar}
     * @param remoteNode
     *            the {@linkplain RemoteNode} the
     *            {@linkplain NodeStatusView} is for
     */
    public NodeStatusView(final ControlBar controlBar) {
      super();
      getStyleClass().add("remote-node-listcell");
      this.controlBar = controlBar;
      this.statusIcon = new StatusIcon(16d, 16d, GuiUtil.COLOR_CLOSED);
      this.helpTextStringProperty = new SimpleStringProperty();
      setHelpText("LOADING...");
      setCursor(Cursor.HAND);
      controlBar.addHelpTextTrigger(this, this.helpTextStringProperty);
    }

    /**
     * {@inheritDoc}
     */
        @Override
        public void updateItem(final String item, final boolean empty) {
            super.updateItem(item, empty);
            if (item != null) {
        if (!isSelected()
            && item.equalsIgnoreCase(getListView()
                .getSelectionModel().getSelectedItem())) {
          // bug where selection is not made on the cell, but exists
          // on the list (isSelected() will still be false immediately
          // following the selection on the list
          getListView().getSelectionModel().select(item);
        }
              this.address = item;
          setText(this.address);
                setGraphic(this.statusIcon);
                ALL.put(this.address, this);
                isSet = true;
            } else if (isSet) {
              ALL.remove(this.address);
            }
        }

    /**
     * Removes an {@linkplain RemoteNode#getAddress()} from the global cache
     *
     * @param address
     *            the {@linkplain RemoteNode#getAddress()} key
     */
        public static void remove(final String address) {
          ALL.remove(address);
        }

    /**
     * Gets a {@linkplain NodeStatusView} from the global cache using it's
     * {@linkplain RemoteNode#getAddress()}
     *
     * @param address
     *            the {@linkplain RemoteNode#getAddress()} key
     * @return the {@linkplain NodeStatusView}
     */
        public static NodeStatusView get(final String address) {
          return ALL.get(address);
        }

    /**
     * Sets the help text for the status view
     *
     * @param commandDesc
     *            the node command description
     */
    protected void setHelpText(final String commandDesc) {
      this.helpTextStringProperty.set(RS.rbLabel(
          KEY.WIRELESS_NODE_REMOTE_STATUS,
          getAddress(), commandDesc));
    }
   
    /**
     * Updates the help text of the node status using a reference to the
     * supplied {@linkplain Command}. Also blinks the
     * {@linkplain NodeStatusView} for about 10 seconds when the
     * {@linkplain ControlBar#getRemoteNode()} has the same
     * {@linkplain RemoteNode#getAddress()} as the the
     * {@linkplain #getRemoteNode()} or indefinitely when it is different.
     *
     * @param command
     *            the {@linkplain Command} to use in the help text
     * @param hasIssue
     *            true when there was an issue with the command or the
     *            {@linkplain RemoteNode} is out-of-sync
     */
    public void updateLastCommand(final Command command, final boolean hasIssue) {
      setHelpText(command.toString());
      final int cycleCount = getAddress().equalsIgnoreCase(
          controlBar.getRemoteNode().getAddress()) ? 40
          : Timeline.INDEFINITE;
      this.statusIcon.setStatusFill(Duration.seconds(1),
          hasIssue ? GuiUtil.COLOR_OPEN : GuiUtil.COLOR_ON,
          GuiUtil.COLOR_CLOSED, cycleCount);
    }
   
    /**
     * @return the {@linkplain RemoteNode#getAddress()}
     */
    public String getAddress() {
      return address;
    }

    /**
     * @return the {@linkplain StatusView}
     */
    public StatusIcon getStatusIcon() {
      return statusIcon;
    }
  }
}
TOP

Related Classes of org.ugate.gui.view.RemoteNodes$NodeStatusView

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.