/* * 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) { } } } } }