/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab 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 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * 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. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * */ package org.myrobotlab.control; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.text.DefaultCaret; import org.fife.ui.autocomplete.AutoCompletion; import org.fife.ui.autocomplete.CompletionProvider; import org.fife.ui.rsyntaxtextarea.FileLocation; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.TextEditorPane; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextScrollPane; import org.myrobotlab.control.widget.Console; import org.myrobotlab.control.widget.FileUtil; import org.myrobotlab.control.widget.ImageButton; import org.myrobotlab.framework.Service; import org.myrobotlab.logging.Logging; import org.myrobotlab.net.BareBonesBrowserLaunch; import org.myrobotlab.service.GUIService; import org.myrobotlab.service.Python; import org.myrobotlab.service.Python.Script; import org.myrobotlab.ui.autocomplete.MRLCompletionProvider; /** * Python GUIService * * @author SwedaKonsult * * use - http://famfamfam.com/lab/icons/silk/previews/index_abc.png - * SILK ICONS */ public class PythonGUI extends ServiceGUI implements ActionListener, MouseListener { static public class EditorPanel { String filename; TextEditorPane editor; JScrollPane panel;// = createEditorPane(); public EditorPanel(Script script) { try { filename = script.getName(); editor = new TextEditorPane(RTextArea.INSERT_MODE, false, FileLocation.create(new File(filename))); editor.setText(script.getCode()); editor.setCaretPosition(0); panel = createEditorPane(); } catch (Exception e) { Logging.logError(e); } } private JScrollPane createEditorPane() { // editor tweaks editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON); editor.setCodeFoldingEnabled(true); editor.setAntiAliasingEnabled(true); // auto-completion if (ac != null) { ac.install(editor); ac.setShowDescWindow(true); } return new RTextScrollPane(editor); } public String getDisplayName() { if (filename.startsWith("Python/examples/")) { return filename.substring("Python/examples/".length()); } else { int begin = filename.lastIndexOf(File.separator); if (begin > 0) { ++begin; } else { begin = 0; } return filename.substring(begin); } } public TextEditorPane getEditor() { return editor; } public String getFilename() { return filename; } } static final long serialVersionUID = 1L; private final static int fileMenuMnemonic = KeyEvent.VK_F; private static final int saveMenuMnemonic = KeyEvent.VK_S; private static final int openMenuMnemonic = KeyEvent.VK_O; private static final int examplesMenuMnemonic = KeyEvent.VK_X; final JFrame top; final JTabbedPane editorTabs; JSplitPane splitPane; final JLabel statusInfo; JMenu examples; HashMap<String, EditorPanel> scripts = new HashMap<String, EditorPanel>(); // TODO - check for outside modification with lastmoddate String currentScriptName; // button bar buttons ImageButton executeButton; ImageButton stopButton; ImageButton openFileButton; ImageButton saveFileButton; // consoles JTabbedPane consoleTabs; final Console javaConsole; final JTextArea pythonConsole; final JScrollPane pythonScrollPane; // auto-completion static CompletionProvider provider; static AutoCompletion ac; int untitledCount = 1; /** * Constructor * * @param boundServiceName * @param myService */ public PythonGUI(final String boundServiceName, final GUIService myService, final JTabbedPane tabs) { super(boundServiceName, myService, tabs); javaConsole = new Console(); pythonConsole = new JTextArea(); pythonScrollPane = new JScrollPane(pythonConsole); // autocompletion - in the constructor so that they can be declared // final // provider = createCompletionProvider(); FIXME - takes forever // ac = new AutoCompletion(provider); provider = null; ac = null; currentScriptName = null; editorTabs = new JTabbedPane(); splitPane = null; statusInfo = new JLabel("Status:"); top = myService.getFrame(); Script s = new Script(String.format("%s%suntitled.swing.%d.py", Service.getCfgDir(), File.separator, untitledCount), ""); addNewEditorPanel(s); } @Override public void actionPerformed(ActionEvent arg0) { Object o = arg0.getSource(); if (o == stopButton) { performStop(); return; } else if (o == executeButton) { performExecute(); return; } else if (o == saveFileButton) { saveFile(); return; } else if (o == openFileButton) { openFile(); return; } if (!(o instanceof JMenuItem)) { return; } JMenuItem m = (JMenuItem) o; if (m.getText().equals("new")) { ++untitledCount; Script s = new Script(String.format("%s%suntitled.%d.py", Service.getCfgDir(), File.separator, untitledCount), ""); addNewEditorPanel(s); } else if (m.getText().equals("save")) { saveFile(); } else if (m.getText().equals("open")) { openFile(); } else if (m.getText().equals("save as")) { saveAsFile(); } else if (m.getText().equals("close")) { closeFile(); } else if (m.getActionCommand().equals("examples")) { // } else if (m.getActionCommand().equals("examples")) { // BareBonesBrowserLaunch.openURL("https://github.com/MyRobotLab/pyrobotlab"); /* * String filename = String.format("Python/examples/%1$s", m.getText()); * Script script = new Script(filename, * FileIO.resourceToString(filename)); addNewEditorPanel(script); */ } } public EditorPanel addNewEditorPanel(Script script) { EditorPanel panel = new EditorPanel(script); editorTabs.addTab(panel.getDisplayName(), panel.panel); log.info(panel.getEditor().getFileFullPath()); // GUIService gui = myService;// FIXME - bad bad bad ... // -------- here ---------------- // TabControl tc = new TabControl(gui, editorTabs, panel.panel, // boundServiceName, panel.getDisplayName(), panel.getFilename()); TabControl2 tc = new TabControl2(self, editorTabs, panel.panel, panel.getFilename()); tc.addMouseListener(this); editorTabs.setTabComponentAt(editorTabs.getTabCount() - 1, tc); currentScriptName = script.getName(); scripts.put(script.getName(), panel); return panel; } public void appendScript(String data) { EditorPanel p = scripts.get(currentScriptName); if (p != null) { p.editor.setText(String.format("%s\n%s", p.editor.getText(), data)); } else { log.error("can't append Script to current"); } } @Override public void attachGUI() { subscribe("publishState", "getState", Python.class); subscribe("finishedExecutingScript"); /** REMOVE IF FLAKEY BUGS APPEAR !! */ subscribe("publishStdOut", "getStdOut", String.class); subscribe("appendScript", "appendScript", String.class); subscribe("startRecording", "startRecording", String.class); subscribe("publishLoadedScript", "addNewEditorPanel", Script.class); myService.send(boundServiceName, "attachPythonConsole"); // myService.send(boundServiceName, "broadcastState"); } public void closeFile() { if (scripts.containsKey(currentScriptName)) { EditorPanel p = scripts.get(currentScriptName); if (p.editor.isDirty()) { saveAsFile(); } p = scripts.get(currentScriptName); scripts.remove(p); editorTabs.remove(p.panel); } else { log.error(String.format("can't closeFile %s", currentScriptName)); } } /** * * @return */ // Never called locally, commenting out. // private CompletionProvider createCompletionProvider() { // // TODO -> LanguageSupportFactory.get().register(editor); // // // A DefaultCompletionProvider is the simplest concrete implementation // // of CompletionProvider. This provider has no understanding of // // language semantics. It simply checks the text entered up to the // // caret position for a match against known completions. This is all // // that is needed in the majority of cases. // return new MRLCompletionProvider(); // } /** * Fill up the file menu with submenu items. * * @param fileMenu */ private void createFileMenu(JMenu fileMenu) { fileMenu.add(createMenuItem("new")); fileMenu.add(createMenuItem("save", saveMenuMnemonic, "control S", null)); fileMenu.add(createMenuItem("save as")); fileMenu.add(createMenuItem("open", openMenuMnemonic, "control O", null)); fileMenu.add(createMenuItem("close")); fileMenu.addSeparator(); } /** * Build the main portion of the view. * * @return */ private JSplitPane createMainPane() { JSplitPane pane = new JSplitPane(); consoleTabs = createTabsPane(); pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, editorTabs, consoleTabs); pane.setDividerLocation(450); return pane; } /** * Helper function to create a menu item. * * @param label * @return */ private JMenuItem createMenuItem(String label) { return createMenuItem(label, -1, null, null); } /** * Helper function to create a menu item. * * @param label * @param vKey * @param accelerator * @param actionCommand * @return */ private JMenuItem createMenuItem(String label, int vKey, String accelerator, String actionCommand) { JMenuItem mi = null; if (vKey == -1) { mi = new JMenuItem(label); } else { mi = new JMenuItem(label, vKey); } if (actionCommand != null) { mi.setActionCommand(actionCommand); } if (accelerator != null) { KeyStroke ctrlCKeyStroke = KeyStroke.getKeyStroke(accelerator); mi.setAccelerator(ctrlCKeyStroke); } mi.addActionListener(this); return mi; } /** * Helper function to create a menu item. * * @param label * @param actionCommand * @return */ // never called locally, commenting out. // private JMenuItem createMenuItem(String label, String actionCommand) { // return createMenuItem(label, -1, null, actionCommand); // } /** * Build the top menu panel. * * @return */ private JPanel createMenuPanel() { JMenuBar menuBar = createTopMenuBar(); JPanel buttonBar = createTopButtonBar(); JPanel menuPanel = new JPanel(new BorderLayout()); menuPanel.add(menuBar, BorderLayout.LINE_START); menuPanel.add(buttonBar); return menuPanel; } /** * Build the tabs pane. * * @return */ private JTabbedPane createTabsPane() { JTabbedPane pane = new JTabbedPane(); pane.addTab("java", javaConsole.getScrollPane()); // pane.setTabComponentAt(pane.getTabCount() - 1, new // TabControl(myService, pane, javaConsole.getScrollPane(), // boundServiceName, "java")); pane.setTabComponentAt(pane.getTabCount() - 1, new TabControl2(self, pane, javaConsole.getScrollPane(), "java")); pane.addTab("python", pythonScrollPane); // pane.setTabComponentAt(pane.getTabCount() - 1, new // TabControl(myService, pane, pythonScrollPane, boundServiceName, // "python")); pane.setTabComponentAt(pane.getTabCount() - 1, new TabControl2(self, pane, pythonScrollPane, "python")); return pane; } /** * Build up the top button menu bar. * * @return */ private JPanel createTopButtonBar() { executeButton = new ImageButton("Python", "execute", this); stopButton = new ImageButton("Python", "stop", this); openFileButton = new ImageButton("Python", "open", this); saveFileButton = new ImageButton("Python", "save", this); JPanel buttonBar = new JPanel(); buttonBar.add(openFileButton); buttonBar.add(saveFileButton); buttonBar.add(stopButton); buttonBar.add(executeButton); return buttonBar; } /** * Build up the top text menu bar. * * @return the menu bar filled with the top-level options. */ private JMenuBar createTopMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("file"); menuBar.add(fileMenu); fileMenu.setMnemonic(fileMenuMnemonic); createFileMenu(fileMenu); // examples ----- examples = new JMenu("examples"); menuBar.add(examples); examples.setMnemonic(examplesMenuMnemonic); // createExamplesMenu(examples); // examples.addActionListener(this); examples.addMouseListener(this); return menuBar; } @Override public void detachGUI() { javaConsole.stopLogging(); unsubscribe("publishState", "getState", Python.class); unsubscribe("finishedExecutingScript"); /** REMOVE IF FLAKEY BUGS APPEAR !! */ unsubscribe("publishStdOut", "getStdOut", String.class); unsubscribe("appendScript", "appendScript", String.class); unsubscribe("startRecording", "startRecording", String.class); } /** * */ public void finishedExecutingScript() { executeButton.deactivate(); stopButton.deactivate(); } /** * * @param j */ public void getState(final Python python) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // merge state with view Script script = python.getScript(); if (script != null) { if (!scripts.containsKey(script.getName())) { addNewEditorPanel(script); } } } }); } /** * * @param data */ public void getStdOut(final String data) { /** REMOVE IF FLAKEY BUGS APPEAR !! */ SwingUtilities.invokeLater(new Runnable() { @Override public void run() { pythonConsole.append(data); } }); } /** * */ @Override public void init() { display.setLayout(new BorderLayout()); display.setPreferredSize(new Dimension(800, 600)); // --------- text menu begin ------------------------ JPanel menuPanel = createMenuPanel(); display.add(menuPanel, BorderLayout.PAGE_START); DefaultCaret caret = (DefaultCaret) pythonConsole.getCaret(); caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); splitPane = createMainPane(); display.add(splitPane, BorderLayout.CENTER); display.add(statusInfo, BorderLayout.PAGE_END); } /* * (non-Javadoc) * * @see org.myrobotlab.control.ServiceGUI#makeReadyForRelease() Shutting down * - check for dirty script and offer to save */ @Override public void makeReadyForRelease() { log.info("makeReadyForRelease"); // Iterator<String> it = scripts.keySet().iterator(); Iterator<Entry<String, EditorPanel>> it = scripts.entrySet().iterator(); while (it.hasNext()) { Map.Entry pairs = it.next(); TextEditorPane e = ((EditorPanel) pairs.getValue()).getEditor(); log.info(String.format("checking script %s", e.getFileFullPath())); if (e.isDirty()) { try { log.info(String.format("saving script / file %s", e.getFileFullPath())); e.save(); } catch (Exception ex) { Logging.logError(ex); } /* * FileLocation fl = FileLocation.create(e.getFileFullPath()); String * filename = JOptionPane.showInputDialog(myService.getFrame(), * "Save File?", name); if (filename != null) { fl = * FileLocation.create(filename); try { e.saveAs(fl); } catch * (IOException e1) { Logging.logException(e1); // TODO Auto-generated * catch block } } */ } } } @Override public void mouseClicked(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseEntered(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent me) { // TODO Auto-generated method stub Object o = me.getSource(); if (o == examples) { BareBonesBrowserLaunch.openURL("https://github.com/MyRobotLab/pyrobotlab"); } if (o instanceof TabControl2) { TabControl2 tc = (TabControl2) o; currentScriptName = tc.getText(); } // log.info(me); } @Override public void mouseReleased(MouseEvent arg0) { // TODO Auto-generated method stub } /** * */ private void openFile() { // TODO does this need to be closed? String newfile = FileUtil.open(top, "*.py"); if (newfile != null) { // editor.setText(newfile); String filename = FileUtil.getLastFileOpened(); Script script = new Script(filename, newfile); addNewEditorPanel(script); statusInfo.setText("Loaded: " + FileUtil.getLastFileOpened()); return; } statusInfo.setText(FileUtil.getLastStatus()); return; } /** * Perform an execute action. */ private void performExecute() { executeButton.activate(); stopButton.deactivate(); javaConsole.startLogging(); // Hmm... noticed this is only local JVM // :) the Python console can be pushed // over the network if (scripts.containsKey(currentScriptName)) { EditorPanel p = scripts.get(currentScriptName); myService.send(boundServiceName, "exec", p.editor.getText()); } else { log.error(String.format("cant exec %s", currentScriptName)); } } /** * Perform the restart action. */ private void performStop() { stopButton.activate(); // executeButton.deactivate(); myService.send(boundServiceName, "stop"); myService.send(boundServiceName, "attachPythonConsole"); } public void saveAsFile() { if (scripts.containsKey(currentScriptName)) { EditorPanel p = scripts.get(currentScriptName); // FIXME - don't create if not necessary if (FileUtil.saveAs(top, p.editor.getText(), currentScriptName)) { currentScriptName = FileUtil.getLastFileSaved(); scripts.remove(p); editorTabs.remove(p.panel); EditorPanel np = addNewEditorPanel(new Script(currentScriptName, p.editor.getText())); editorTabs.setSelectedComponent(np.panel); } } else { log.error(String.format("cant saveAsFile %s", currentScriptName)); } // TODO do we need to handle errors with permissions? } /** * */ public void saveFile() { if (scripts.containsKey(currentScriptName)) { EditorPanel p = scripts.get(currentScriptName); if (FileUtil.save(top, p.editor.getText(), currentScriptName)) { currentScriptName = FileUtil.getLastFileSaved(); scripts.remove(p); editorTabs.remove(p.panel); EditorPanel np = addNewEditorPanel(new Script(currentScriptName, p.editor.getText())); editorTabs.setSelectedComponent(np.panel); // sdfafafdds } } else { log.error(String.format("cant saveFile %s", currentScriptName)); } // TODO do we need to handle errors with permissions? } public void startRecording(String filename) { addNewEditorPanel(new Script(filename, "")); } }