Package org.octabyte.fxshell.javafx

Source Code of org.octabyte.fxshell.javafx.TerminalScene

package org.octabyte.fxshell.javafx;

import org.octabyte.fxshell.terminal.TerminalEvenProcessor;
import org.octabyte.fxshell.terminal.TerminalState;
import org.octabyte.fxshell.terminal.TerminalStateUpdateListener;
import org.octabyte.fxshell.terminal.event.CharacterEvent;
import org.octabyte.fxshell.terminal.event.KeyboardEvent;
import org.octabyte.fxshell.terminal.event.NewLineEvent;
import org.octabyte.fxshell.utils.Pair;
import org.octabyte.fxshell.utils.PairUtils;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.BlendMode;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.datatransfer.StringSelection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
* @author kobylsa on 03/02/14.
*/
public class TerminalScene extends Scene implements TerminalStateUpdateListener {
    private static final Logger log = LoggerFactory.getLogger(TerminalScene.class);

    private final static Font font = Font.font("Lucida Console", FontWeight.BOLD, 16);
    private final static float lineHeight = Toolkit.getToolkit().getFontLoader().getFontMetrics(font).getLineHeight();
    private final static float chWidth = Toolkit.getToolkit().getFontLoader().computeStringWidth(Character.toString(' '), font);
    private final static float fontAscentDiff = lineHeight - Toolkit.getToolkit().getFontLoader().getFontMetrics(font).getAscent();

    private final TerminalState termState;
    private final TerminalEvenProcessor eventProcessor;
    private final GraphicsContext gc;

    private List<Line> lines = Collections.emptyList();

    private TermPoint selStartPoint = null;
    private TermPoint selCurPoint = null;

    public TerminalScene(Parent root, final GraphicsContext gc, TerminalState termState, TerminalEvenProcessor eventProcessor) {
        super(root);

        this.termState = termState;
        this.eventProcessor = eventProcessor;
        this.gc = gc;

        fullRefresh(gc);
        this.setOnKeyPressed(new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent keyEvent) {
                handleKeyEvent(keyEvent);
            }
        });
        this.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                handleMousePressed(mouseEvent);
            }
        });
        this.setOnMouseReleased(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                handleMouseRelease(mouseEvent);
            }
        });
        this.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                handleMouseDragged(mouseEvent);
            }
        });
        this.widthProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) {
                gc.getCanvas().setWidth(newSceneWidth.doubleValue());
                fullRefresh(gc);
            }
        });
        this.heightProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) {
                gc.getCanvas().setHeight(newSceneHeight.doubleValue());
                fullRefresh(gc);
            }
        });

        this.termState.addUpdateListener(this);
    }

    private void handleMouseDragged(MouseEvent mouseEvent) {
        if ( selStartPoint != null ) {
            selCurPoint = getTermPoint(mouseEvent.getSceneY(), mouseEvent.getSceneX());
            drawSelection(gc);
        }
    }

    private void handleMouseRelease(MouseEvent mouseEvent) {
        final TermPoint releasePoint = getTermPoint(mouseEvent.getSceneY(), mouseEvent.getSceneX());
        Pair<Integer, Integer> xpair = PairUtils.getAscendingPair(releasePoint.x, selStartPoint.x);
        Pair<Integer, Integer> ypair = PairUtils.getAscendingPair(releasePoint.y, selStartPoint.y);

        if ( xpair.left < 0 ) xpair = new Pair<>(0, xpair.right);
        if ( ypair.left < 0 ) ypair = new Pair<>(0, ypair.right);

        StringBuilder selection = new StringBuilder();
        for(int yidx = ypair.left; yidx <= ypair.right; ++yidx) {
            for(int xidx = xpair.left; xidx <= xpair.right; ++xidx) {
                if ( yidx < lines.size() && xidx < lines.get(yidx).getLine().length() ) {
                    selection.append( lines.get(yidx).getLine().charAt(xidx) );
                } else {
                    selection.append(' ');
                }
            }
            if ( yidx != ypair.right ) {
                selection.append('\n');
            }
        }
        StringSelection stringSelection = new StringSelection(selection.toString());
        java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);

        selStartPoint = null;
        drawScreen(gc);
    }

    private void handleMousePressed(MouseEvent mouseEvent) {
        log.debug("Mouse pressed: " + mouseEvent);
        selStartPoint = getTermPoint(mouseEvent.getSceneY(), mouseEvent.getSceneX());
        drawScreen(gc);
    }

    private static TermPoint getTermPoint(double sceneY, double sceneX) {
        final int chY = (int)(sceneY /lineHeight);
        final int chX = (int)(sceneX /chWidth);
        return new TermPoint(chX, chY);
    }

    private void handleKeyEvent(KeyEvent keyEvent) {
        if ( keyEvent.getCode() == KeyCode.ENTER ) {
            eventProcessor.onTerminalEvent(new NewLineEvent());
        } else if ( keyEvent.isControlDown() ) {
            eventProcessor.onTerminalEvent(
                    KeyboardEvent.builder(keyEvent.getCode())
                            .ctrlDown(keyEvent.isControlDown())
                            .shiftDown(keyEvent.isShiftDown())
                            .build()
            );
        } else if ( !keyEvent.getText().isEmpty() ) {
            eventProcessor.onTerminalEvent(
                    new CharacterEvent(keyEvent.getCode(), keyEvent.getText().charAt(0))
            );
        } else {
            eventProcessor.onTerminalEvent(
                    KeyboardEvent.builder(keyEvent.getCode())
                            .ctrlDown(keyEvent.isControlDown())
                            .shiftDown(keyEvent.isShiftDown())
                            .build()
            );
        }

        keyEvent.consume();
    }

    @Override
    public void onUpdate(TerminalState state) {
        fullRefresh(gc);
    }

    private void fullRefresh(GraphicsContext gc) {
        refreshLines(gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
        drawScreen(gc);
    }

    private void drawScreen(GraphicsContext gc) {
        final Color bgColor = Color.BLACK;
        final Color textColor = Color.rgb(0x00, 0xFF, 0x00);

        gc.setFill(bgColor);
        gc.fillRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());

        gc.setFont(font);
        gc.setFill(textColor);

        float yPos = lineHeight;
        for(Line line: lines) {
            final StringBuilder currTextBuffer = line.getLine();
            final int lineCurPos = line.getCursorPos();

            gc.fillText(currTextBuffer.toString(), 0, yPos);
            if ( lineCurPos != -1 ) {
                final float curXPos = lineCurPos * chWidth;
                final float curYPos = yPos-(lineHeight)+fontAscentDiff;
                gc.fillRect(curXPos, curYPos, chWidth, lineHeight);
                if ( lineCurPos != currTextBuffer.length() ) {
                    gc.setFill(bgColor);
                    gc.fillText(
                            currTextBuffer.charAt(lineCurPos) + "",
                            curXPos,
                            yPos);
                    gc.setFill(textColor);
                }
            }
            yPos += lineHeight;
        }
    }

    private void drawSelection(GraphicsContext gc) {
        if ( selStartPoint != null ) {
            this.drawScreen(gc);

            final Pair<Integer, Integer> xpair = PairUtils.getAscendingPair(selCurPoint.x, selStartPoint.x);
            final Pair<Integer, Integer> ypair = PairUtils.getAscendingPair(selCurPoint.y, selStartPoint.y);

            double width = ((xpair.right - xpair.left)+1)*chWidth;
            double height = ((ypair.right - ypair.left)+1)*lineHeight+fontAscentDiff;
            final float selStartX = xpair.left*chWidth;
            final float selStartY = ypair.left*lineHeight;

            gc.save();
            try {
                gc.setGlobalBlendMode(BlendMode.EXCLUSION);
                gc.fillRect(selStartX, selStartY, width, height);
            } finally {
                gc.restore();
            }
        }
    }

    private void refreshLines(double width, double height) {
        final List<Line> screenLines = new LinkedList<Line>();

        // start from the end, because the lines which are visible on the screen are the N last ones.
        int lastIdx = termState.getLines().size() - 1;
        for (int i = lastIdx; i != -1 && (screenLines.size()*lineHeight+lineHeight) <= height; --i) {
            // as the order is reverse, grow from the head
            screenLines.add(0, new Line(new StringBuilder(), -1));

            float lineWidth = 0;
            final String termLine = termState.getLines().get(i).getCompleteLine();

            int printLine = 0;
            for(int j = 0; j != termLine.length(); ++j) {
                final char ch = termLine.charAt(j);
                final float chWidth = Toolkit.getToolkit().getFontLoader().computeStringWidth(Character.toString(ch), font);

                // line is too big to fit into one screen line
                if ( (lineWidth+chWidth) > width ) {
                    screenLines.add(++printLine, new Line(new StringBuilder(), -1));
                    lineWidth = chWidth;
                } else {
                    lineWidth += chWidth;
                }

                screenLines.get(printLine).getLine().append(ch);
            }

            // the first line, calculate cursor position
            if ( i == lastIdx ) {
                int curLine = 0;
                int curPos = termState.getCursorPos();
                while ( curLine != printLine
                        && (curPos - screenLines.get(curLine).getLine().length()) >= ) {
                    curPos -= screenLines.get(curLine).getLine().length();
                    ++curLine;
                }
                screenLines.get(curLine).setCursorPos(curPos);
            }
        }

        this.lines = screenLines;
    }
}
TOP

Related Classes of org.octabyte.fxshell.javafx.TerminalScene

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.