/** * * @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.widget; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; 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.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.KeyStroke; import org.fife.ui.autocomplete.CompletionProvider; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rtextarea.RTextScrollPane; import org.myrobotlab.control.ServiceGUI; import org.myrobotlab.control.TabControl2; import org.myrobotlab.framework.Service; import org.myrobotlab.service.GUIService; import org.myrobotlab.service.Python; /** * Editor * * General purpose swing editor TODO generalize for Python & Arduino * * @author GroG * */ public class Editor extends ServiceGUI implements ActionListener { static final long serialVersionUID = 1L; final static int fileMenuMnemonic = KeyEvent.VK_F; static final int saveMenuMnemonic = KeyEvent.VK_S; static final int openMenuMnemonic = KeyEvent.VK_O; static final int examplesMenuMnemonic = KeyEvent.VK_X; final JFrame top; final protected RSyntaxTextArea textArea; JScrollPane editorScrollPane; final JTabbedPane editorTabs; public JProgressBar progress = new JProgressBar(0, 100); JSplitPane splitPane; final JLabel statusLabel; public final JLabel status; // TODO - check for outside modification with lastmoddate File currentFile; String currentFilename; // menu JMenu fileMenu = createFileMenu(); JMenu editMenu = createEditMenu(); JMenu examplesMenu = new JMenu("Examples");; JMenu toolsMenu = createToolsMenu(); JMenu helpMenu = createHelpMenu(); JMenuBar menuBar = createMenuBar(); JPanel buttonBar = new JPanel(); JPanel menuPanel = createMenuPanel(); // button bar buttons ImageButton executeButton; ImageButton restartButton; ImageButton openFileButton; ImageButton saveFileButton; // consoles JTabbedPane consoleTabs; public final Console console; // autocompletion // final CompletionProvider provider; // final AutoCompletion ac; String syntaxStyle; /** * Constructor * * @param boundServiceName * @param myService */ public Editor(final String boundServiceName, final GUIService myService, final JTabbedPane tabs, String syntaxStyle) { super(boundServiceName, myService, tabs); this.syntaxStyle = syntaxStyle; console = new Console(); // provider = createCompletionProvider(); // ac = new AutoCompletion(provider); // FYI - files are on the "Arduino" service not on the GUIService - // these // potentially are remote objects currentFile = null; currentFilename = null; textArea = new RSyntaxTextArea(); editorScrollPane = null; editorTabs = new JTabbedPane(); splitPane = null; statusLabel = new JLabel("Status:"); status = new JLabel(""); top = myService.getFrame(); textArea.getInputMap().put(KeyStroke.getKeyStroke("F3"), "find-action"); textArea.getInputMap().put(KeyStroke.getKeyStroke("ctrl F"), "find-action"); /* * RSyntaxTextAreaFindAndReplaceable findAndReplaceable = new * RSyntaxTextAreaFindAndReplaceable(); editArea.getActionMap().put( * "find-action", new FindAndReplaceDialog( findAndReplaceable ) ); */ } @Override public void actionPerformed(ActionEvent arg0) { Object o = arg0.getSource(); if (o == restartButton) { performRestart(); 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("Save")) { saveFile(); } else if (m.getText().equals("Open")) { openFile(); } else if (m.getText().equals("Save As")) { saveAsFile(); } else if (m.getText().equals("Find")) { new FindAndReplaceDialog(this); } /* * else if (m.getActionCommand().equals("examples")) { * textArea.setText(FileIO * .getResourceFile(String.format("python/examples/%1$s", m.getText()))); } */ } public ImageButton addImageButtonToButtonBar(String resourceDir, String name, ActionListener al) { ImageButton ret = new ImageButton(resourceDir, name, al); buttonBar.add(ret); return ret; } @Override public void attachGUI() { subscribe("publishState", "getState", Python.class); subscribe("finishedExecutingScript"); subscribe("publishStdOut", "getStdOut", String.class); // myService.send(boundServiceName, "broadcastState"); } 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 JavaCompletionProvider(); } JMenu createEditMenu() { editMenu = new JMenu("Edit"); editMenu.add(createMenuItem("Undo")); editMenu.add(createMenuItem("Redo")); editMenu.addSeparator(); editMenu.add(createMenuItem("Cut")); editMenu.add(createMenuItem("Copy")); // editMenu.add(createMenuItem("save", saveMenuMnemonic, "control S", // null)); editMenu.addSeparator(); editMenu.add(createMenuItem("Find", openMenuMnemonic, "CTRL+F", "Find")); // editMenu.add(createMenuItem("Format")); return editMenu; } /** * Build the editor pane. * * @return */ JScrollPane createEditorPane() { textArea.setSyntaxEditingStyle(syntaxStyle); textArea.setCodeFoldingEnabled(true); textArea.setAntiAliasingEnabled(true); // autocompletion // ac.install(textArea); // ac.setShowDescWindow(true); return new RTextScrollPane(textArea); } JMenu createFileMenu() { JMenu menu = new JMenu("File"); menu.setMnemonic(fileMenuMnemonic); menu.add(createMenuItem("New")); menu.add(createMenuItem("Save", saveMenuMnemonic, "control S", null)); menu.add(createMenuItem("Save As")); menu.add(createMenuItem("Open", openMenuMnemonic, "control O", null)); menu.addSeparator(); return menu; } JMenu createHelpMenu() { helpMenu = new JMenu("Help"); return helpMenu; } /** * Build the main portion of the view. * * @return */ JSplitPane createMainPane() { JSplitPane pane = new JSplitPane(); JPanel lowerPanel = new JPanel(); lowerPanel.setLayout(new BorderLayout()); consoleTabs = createTabsPane(); lowerPanel.add(consoleTabs, BorderLayout.CENTER); progress.setForeground(Color.green); lowerPanel.add(progress, BorderLayout.SOUTH); editorScrollPane = createEditorPane(); pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, editorScrollPane, lowerPanel); pane.setDividerLocation(440); return pane; } /** * Build up the top text menu bar. * * @return the menu bar filled with the top-level options. */ JMenuBar createMenuBar() { menuBar = new JMenuBar(); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(examplesMenu); menuBar.add(toolsMenu); menuBar.add(createHelpMenu()); return menuBar; } /** * Helper function to create a menu item. * * @param label * @return */ 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 */ 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 */ JMenuItem createMenuItem(String label, String actionCommand) { return createMenuItem(label, -1, null, actionCommand); } JPanel createMenuPanel() { JPanel menuPanel = new JPanel(new BorderLayout()); menuPanel.add(menuBar, BorderLayout.LINE_START); menuPanel.add(buttonBar); return menuPanel; } JTabbedPane createTabsPane() { JTabbedPane pane = new JTabbedPane(); pane.addTab("console", console.getScrollPane()); pane.setTabComponentAt(pane.getTabCount() - 1, new TabControl2(self, pane, console.getScrollPane(), "console")); return pane; } JMenu createToolsMenu() { toolsMenu = new JMenu("Tools"); return toolsMenu; } @Override public void detachGUI() { console.stopLogging(); unsubscribe("publishStdOut", "getStdOut", String.class); unsubscribe("finishedExecutingScript"); unsubscribe("publishState", "getState", Python.class); } public void finishedExecutingScript() { executeButton.deactivate(); } public void getState(Service j) { // TODO set GUIService state debug from Service data } public RSyntaxTextArea getTextArea() { return textArea; } @Override public void init() { display.setLayout(new BorderLayout()); display.setPreferredSize(new Dimension(800, 600)); // default text based menu display.add(menuPanel, BorderLayout.PAGE_START); splitPane = createMainPane(); display.add(splitPane, BorderLayout.CENTER); JPanel s = new JPanel(new FlowLayout(FlowLayout.LEFT)); s.add(statusLabel); s.add(status); display.add(s, BorderLayout.PAGE_END); } /** * */ void openFile() { // TODO does this need to be closed? String newfile = FileUtil.open(top, "*.py"); if (newfile != null) { textArea.setText(newfile); statusLabel.setText("Loaded: " + FileUtil.getLastFileOpened()); return; } statusLabel.setText(FileUtil.getLastStatus()); return; } /** * Perform an execute action. */ void performExecute() { executeButton.activate(); restartButton.deactivate(); console.startLogging(); // Hmm... noticed this is only local JVM // :) the Python console can be pushed // over the network myService.send(boundServiceName, "attachPythonConsole"); myService.send(boundServiceName, "exec", textArea.getText()); } /** * Perform the restart action. */ void performRestart() { restartButton.activate(); executeButton.deactivate(); myService.send(boundServiceName, "stop"); } /** * */ void saveAsFile() { // TODO do we need to handle errors with permissions? if (FileUtil.saveAs(top, textArea.getText(), currentFilename)) currentFilename = FileUtil.getLastFileSaved(); } /** * */ void saveFile() { // TODO do we need to handle errors with permissions? if (FileUtil.save(top, textArea.getText(), currentFilename)) currentFilename = FileUtil.getLastFileSaved(); } public void setStatus(String s) { status.setText(s); } }