/*
* Copyright (C) 2013 Vinu K.N
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.domainmath.gui.octave;
import dev.exec.util.ExecHelper;
import dev.exec.util.ExecProcessor;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.*;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import javax.swing.text.NavigationFilter;
import javax.swing.text.Position;
import org.domainmath.gui.MainFrame;
import org.domainmath.gui.editor.AutoCompleteListCellRenderer;
import org.domainmath.gui.editor.OctaveM;
import org.domainmath.gui.octave.OctavePanel.OctaveEngine;
import org.domainmath.gui.preferences.PreferencesDlg;
import org.domainmath.gui.tools.files.FilesDialog;
import org.domainmath.gui.workspace.WorkspacePanel;
import org.fife.ui.autocomplete.*;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
/**
* It creates a panel contains output area which is display output from Octave
* and command area for input data to Octave. This is the core of DMI.
* @author Vinu K.N
*/
public class OctavePanel extends JPanel implements ExecProcessor{
public RSyntaxTextArea commandArea;
public JTextArea outputArea = new JTextArea();
OctaveEngine oc = new OctaveEngine();
public static String line2;
StringBuilder b = new StringBuilder();
private final MainFrame frame;
private PreferencesDlg preferencesDlg;
public static List history =Collections.synchronizedList(new ArrayList());
private int id=1;
private JPopupMenu _p1;
private int histLine;
private ExecHelper exh;
private String lastCommand;
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/domainmath/gui/octave/resources/octavepanel_en");
/**
* Number of commands.
*/
private int historyIndex;
/**
* Construct OctavePanel.
*
*/
public OctavePanel(MainFrame frame) {
this.frame = frame;
init();
historyIndex=0;
}
/**
* Start Octave.
*/
public void start() {
try {
if (exh == null) {
MainFrame.statusPanel.changeStatus(bundle.getString("connecting.octave.status"));
String path2 =frame.getOctavePath()+" "+frame.getCmdLineOptions();
String addpath = " --path "+Character.toString('"') +System.getProperty("user.dir")+File.separator+"scripts"+Character.toString('"');
exh = ExecHelper.exec(this, path2+addpath);
System.err.println(path2+addpath);
oc.find(frame.getStartupCmd());
oc.find("warning off");
MainFrame.statusPanel.changeStatus(bundle.getString("octave.ready"));
}
} catch (Exception ex) {
MainFrame.statusPanel.changeStatus(bundle.getString("octave.connection.failed"));
JOptionPane.showMessageDialog(frame, bundle.getString("octave.connection.failed"),bundle.getString("DomainMathIDE.messagebox.title"),JOptionPane.ERROR_MESSAGE);
preferencesDlg = new PreferencesDlg(frame,true);
preferencesDlg.setLocationRelativeTo(this);
preferencesDlg.setVisible(true);
}
}
/**
* Get last entered command.
* @return lastCommand
*/
public String getLastCommand() {
return lastCommand;
}
/**
* Set last entered command.
* @param lastCommand
*/
public void setLastCommand(String lastCommand) {
this.lastCommand = lastCommand;
}
/**
* Stop Octave.
* @see exit()
*/
public void quit() {
oc.exit();
}
/**
* Create auto completion.It returns DefaultCompletionProvider.
* @return provider
*/
private CompletionProvider createCompletionProvider() {
DefaultCompletionProvider provider = new DefaultCompletionProvider();
JList l = new JList();
AutoCompleteListCellRenderer cellRender = new AutoCompleteListCellRenderer(l.getFont(),
l.getBackground(),l.getForeground(),
l.getSelectionBackground(),l.getSelectionForeground());
provider.setListCellRenderer(cellRender);
OctaveM _m = new OctaveM();
List a = _m.getKey("DomainMath_OctaveAutoComplete.ini");
for(int i=0;i<a.size();i++) {
provider.addCompletion(new BasicCompletion(provider, a.get(i).toString()));
}
return provider;
}
public void updateConsole() {
Rectangle rect = new Rectangle(MainFrame.octavePanel.commandArea.getBounds());
((JComponent) MainFrame.octavePanel.outputArea.getParent()).scrollRectToVisible(rect);
}
/**
* Initiate Octave Panel.
*/
private void init() {
outputArea.setFont(frame.preferencesDlg.getConsoleFont());
commandArea =new RSyntaxTextArea();
commandArea.setFont(frame.preferencesDlg.getConsoleFont());
commandArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_OCTAVE);
outputArea.setDragEnabled(true);
commandArea.setToolTipText(bundle.getString("commandArea.tooltip"));
commandArea.append(">>");
outputArea.setBorder(null);
commandArea.setNavigationFilter(new NavigationFilterPrefixWithBackspace(2,commandArea));
needOct(true);
setLayout(new BorderLayout());
KeyStroke key = KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER, 0);
commandArea.getInputMap().put(key, new ExecuteAction(commandArea,oc));
KeyStroke key2 = KeyStroke.getKeyStroke(
KeyEvent.VK_SPACE, ActionEvent.CTRL_MASK);
commandArea.getInputMap().put(key2, new CtrlSpaceAction(commandArea,oc));
outputArea.setEditable(false);
commandArea.setBorder(null);
this.setBackground(commandArea.getBackground());
add(outputArea,BorderLayout.NORTH);
add(commandArea,BorderLayout.CENTER);
_p1 = new JPopupMenu();
JMenuItem _copy = new JMenuItem(bundle.getString("copyItem.text"));
JMenuItem _clear = new JMenuItem(bundle.getString("clearItem.text"));
JMenuItem _selectAll = new JMenuItem(bundle.getString("selextAllItem.text"));
_p1.add(_copy);
_p1.add(_selectAll);
outputArea.add(_p1);
// do copy in output area.
_copy.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
outputArea.copy();
}
});
// do select all in output area.
_selectAll.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
outputArea.selectAll();
}
});
// do clear text in output area.
_clear.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
clear();
}
});
// show pop-up menu.
outputArea.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
outputArea.requestFocusInWindow();
showPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
outputArea.requestFocusInWindow();
showPopup(e);
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
});
// display history.
commandArea.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent evt) {
if( evt.getKeyCode() == KeyEvent.VK_UP) { // if pressing up key, show last command.
if ( histLine <history.size() ) {
histLine++;
showHistoryLine();
commandArea.selectAll();
}
evt.consume();
}else if( evt.getKeyCode() == KeyEvent.VK_DOWN) { // if pressing down key show previous command.
if ( histLine == 0 ) {
return ;
}
histLine--;
showHistoryLine();
}
}
});
}
/**
* Show history.
*/
private void showHistoryLine() {
String showline;
if(histLine != 0) {
showline = (String)history.get( history.size() - histLine);
if(frame.isWantedToClearText()) {
commandArea.setText(">>");
commandArea.append(showline);
}else{
commandArea.append(showline);
}
}
}
/**
* Show popup menu in output area.
* @param e
*/
private void showPopup(MouseEvent e){
if( e.isPopupTrigger() ) {
_p1.show(e.getComponent(), e.getX(), e.getY());
}
}
/**
* Evaluate commands for internal purpose.
* @param c
*/
public void evaluate(String c) {
if(WorkspacePanel.automaticRefreshCheckBoxMenuItem.isSelected()) {
System.out.println(c);
oc.find(c);
}
}
/**
* Evaluate command. It will append command to output area,reload workspace and add command to history.
* @param c
*/
public void evalWithOutput(String c) {
outputArea.append(">> ");
id++;
outputArea.append(c+"\n");
oc.find(c);
if(MainFrame.workspace.automaticRefreshCheckBoxMenuItem.isSelected()) {
MainFrame.octavePanel.evaluate("DomainMath_OctaveVariables('"+MainFrame.parent_root+"DomainMath_OctaveVariables.dat',whos);");
MainFrame.workspace.reload();
MainFrame.workspace.reload();
}
// add command to history.
MainFrame.histArea.append(c+"\n");
history.add(c);
}
/**
* Add a '>> ' before command.
* @param s
*/
public void setOctaveTag(String s) {
outputArea.append(">> ");
id++;
outputArea.append(s+"\n");
}
/**
* This method is similar to evalWithOutput,but you can not get history command while typing up key or down key.
* @param c
*/
public void eval(String c) {
outputArea.append(">> ");
id++;
outputArea.append(c+"\n");
oc.find(c);
if(MainFrame.workspace.automaticRefreshCheckBoxMenuItem.isSelected()) {
MainFrame.octavePanel.evaluate("DomainMath_OctaveVariables('"+MainFrame.parent_root+"DomainMath_OctaveVariables.dat',whos);");
MainFrame.workspace.reload();
MainFrame.workspace.reload();
}
//add command to history.
MainFrame.histArea.append(c+"\n");
}
/**
* Update output area according to output from Octave.
* @param textArea
* @param line
* @see dev.exec.util.ExecProcessor
*/
private void updateTextArea(JTextArea textArea, String line) {
line = line.replaceAll(">>", "");
line = line.replaceAll("debug>", "");
outputArea.append(line);
updateConsole();
}
@Override
public void processNewInput(String input) {
updateTextArea(outputArea, input);
}
@Override
public void processNewError(String error) {
updateTextArea(outputArea, error);
}
@Override
public void processEnded(int exitValue) {
exh = null;
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
/**
* It manipulate Octave.
*/
class OctaveEngine {
/**
* Kill process of Octave.
*/
public void exit() {
try{
exh.exit();
}catch(Exception e) {
}
}
/**
* Evaluate command. Internal purpose only.
* @param c
*/
public void find(String c) {
_eval(c);
}
/**
* Evaluate command. It does not keep history.
* @param c
*/
public void eval(String c) {
outputArea.append(">> ");
outputArea.append(c+"\n");
id++;
_eval(c);
if(WorkspacePanel.automaticRefreshCheckBoxMenuItem.isSelected()) {
MainFrame.octavePanel.evaluate("DomainMath_OctaveVariables('"+MainFrame.parent_root+"DomainMath_OctaveVariables.dat',whos);");
MainFrame.workspace.reload();
MainFrame.workspace.reload();
}
}
/**
* Evaluate command. It does not keep history.
* @param c
* @param tag
*/
public void eval(String c,String tag) {
outputArea.append(tag);
outputArea.append(c+"\n");
id++;
_eval(c);
if(!c.contains("input") ||WorkspacePanel.automaticRefreshCheckBoxMenuItem.isSelected()) {
MainFrame.octavePanel.evaluate("DomainMath_OctaveVariables('"+MainFrame.parent_root+"DomainMath_OctaveVariables.dat',whos);");
MainFrame.workspace.reload();
MainFrame.workspace.reload();
}
}
} // end of OctaveEngine.
/**
* Evaluate command. Internal purpose only.
* @param text
*/
public void _eval(String text) {
if (exh != null) {
exh.println(text);
}
}
public void executeAction(RSyntaxTextArea area) {
historyIndex++;
String text= area.getText().replaceAll(">>", "");
switch (text) {
case "exit":
frame.exitApp();
break;
case "clc":
case "home":
outputArea.setText("");
break;
default:
oc.eval(text,">> ");
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
DateFormat.MEDIUM,
Locale.getDefault());
String t="# -- "+formatter.format(new Date())+" -- #";
//add history.
if((historyIndex%10) == 0) {
MainFrame.histArea.append(t+"\n");
}
MainFrame.histArea.append(text+"\n");
setLastCommand(text);
String s = getLastCommand();
if ( s.length() != 0 ) {
history.add( s );
}
if(frame.isWantedToClearText()) {
System.out.println(frame.isWantedToClearText());
area.setText(">>");
}else{
area.setSelectionStart(0);
area.setSelectionEnd(area.getText().length());
}
updateConsole();
break;
}
}
/**
* Add auto completion in command area.
* @param need
*/
public void needOct(boolean need) {
AutoCompletion ac = new AutoCompletion(createCompletionProvider());
KeyStroke key = KeyStroke.getKeyStroke(
KeyEvent.VK_TAB, 0);
ac.setTriggerKey(key);
if(need) {
ac.install(commandArea);
}
else {
ac.uninstall();
}
}
/**
* Clear output area.
*/
public void clear() {
outputArea.setText("");
}
/**
* Execute a command.
*/
class ExecuteAction extends AbstractAction {
private RSyntaxTextArea area;
private final OctaveEngine oc;
/**
* Construct ExecuteAction.
* @param area
* @param oc
*/
public ExecuteAction(RSyntaxTextArea area,OctaveEngine oc) {
this.area=area;
this.oc=oc;
}
@Override
public void actionPerformed(ActionEvent e) {
executeAction(area);
}
}
class CtrlSpaceAction extends AbstractAction {
private RSyntaxTextArea area;
private final OctaveEngine oc;
/**
* Number of commands.
*/
private int i;
/**
* Construct ExecuteAction.
* @param area
* @param oc
*/
public CtrlSpaceAction(RSyntaxTextArea area,OctaveEngine oc) {
this.area=area;
this.oc=oc;
i=0;
}
@Override
public void actionPerformed(ActionEvent e) {
File f = new File(MainFrame.dirComboBox.getSelectedItem().toString());
if(f.exists()) {
FilesDialog filesDialog = new FilesDialog(frame,true);
filesDialog.setLocationRelativeTo(frame);
filesDialog.setVisible(true);
}
}
}
class NavigationFilterPrefixWithBackspace extends NavigationFilter{
private final int prefixLength;
private final Action deletePrevious;
public NavigationFilterPrefixWithBackspace(int prefixLength,JTextComponent component) {
this.prefixLength = prefixLength;
deletePrevious =component.getActionMap().get("delete-previous");
component.getActionMap().put("delete-previous",new BackspaceAction());
component.setCaretPosition(prefixLength);
}
@Override
public void setDot(NavigationFilter.FilterBypass fb,int dot,Position.Bias bias) {
fb.setDot(Math.max(dot, this.prefixLength), bias);
}
@Override
public void moveDot(NavigationFilter.FilterBypass fb,int dot,Position.Bias bias) {
fb.moveDot(Math.max(dot, this.prefixLength), bias);
}
private class BackspaceAction extends AbstractAction{
@Override
public void actionPerformed(ActionEvent e) {
JTextComponent component =(JTextComponent)e.getSource();
try{
if(component.getCaretPosition() > prefixLength) {
deletePrevious.actionPerformed(null);
}
}catch(Exception ex) {
}
}
}
}
}