From 223ba9f75456c42d73f2b6e156c93982acc07960 Mon Sep 17 00:00:00 2001 From: benfry Date: Thu, 6 Dec 2012 20:17:06 +0000 Subject: [PATCH] incorporating updates for experimental mode --- app/.classpath | 4 + app/.classpath_macosx | 20 - app/.classpath_vista | 21 - .../processing/mode/java2/DebugEditor.java | 321 +++++ app/src/processing/mode/java2/ErrorBar.java | 373 ++++++ .../mode/java2/ErrorCheckerService.java | 1097 +++++++++++++++++ .../processing/mode/java2/ErrorMarker.java | 36 + .../processing/mode/java2/ErrorWindow.java | 374 ++++++ .../mode/java2/ImportStatement.java | 51 + app/src/processing/mode/java2/Problem.java | 160 +++ app/src/processing/mode/java2/TextArea.java | 16 +- .../mode/java2/TextAreaPainter.java | 167 ++- .../mode/java2/XQConsoleToggle.java | 131 ++ .../processing/mode/java2/XQErrorTable.java | 161 +++ .../processing/mode/java2/XQPreprocessor.java | 245 ++++ java2/mode/readme.txt | 3 + todo.txt | 2 - 17 files changed, 3134 insertions(+), 48 deletions(-) delete mode 100644 app/.classpath_macosx delete mode 100755 app/.classpath_vista create mode 100755 app/src/processing/mode/java2/ErrorBar.java create mode 100755 app/src/processing/mode/java2/ErrorCheckerService.java create mode 100755 app/src/processing/mode/java2/ErrorMarker.java create mode 100755 app/src/processing/mode/java2/ErrorWindow.java create mode 100755 app/src/processing/mode/java2/ImportStatement.java create mode 100755 app/src/processing/mode/java2/Problem.java create mode 100755 app/src/processing/mode/java2/XQConsoleToggle.java create mode 100755 app/src/processing/mode/java2/XQErrorTable.java create mode 100755 app/src/processing/mode/java2/XQPreprocessor.java create mode 100644 java2/mode/readme.txt diff --git a/app/.classpath b/app/.classpath index 77e975316..6fad01c59 100644 --- a/app/.classpath +++ b/app/.classpath @@ -21,5 +21,9 @@ + + + + diff --git a/app/.classpath_macosx b/app/.classpath_macosx deleted file mode 100644 index 26775c499..000000000 --- a/app/.classpath_macosx +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/.classpath_vista b/app/.classpath_vista deleted file mode 100755 index c7ec5acb9..000000000 --- a/app/.classpath_vista +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/processing/mode/java2/DebugEditor.java b/app/src/processing/mode/java2/DebugEditor.java index 3bbbe4325..eb41c5e2b 100755 --- a/app/src/processing/mode/java2/DebugEditor.java +++ b/app/src/processing/mode/java2/DebugEditor.java @@ -17,8 +17,11 @@ */ package processing.mode.java2; +import java.awt.BorderLayout; +import java.awt.CardLayout; import java.awt.Color; import java.awt.EventQueue; +import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; @@ -28,8 +31,16 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.swing.Box; +import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.EtchedBorder; +import javax.swing.table.TableModel; import javax.swing.text.Document; import processing.app.*; import processing.app.syntax.JEditTextArea; @@ -43,6 +54,9 @@ import processing.mode.java.JavaEditor; * debuggers current line). * * @author Martin Leopold + * @author Manindra Moharana <me@mkmoharana.com> + * + * */ public class DebugEditor extends JavaEditor implements ActionListener { // important fields from superclass @@ -85,6 +99,36 @@ public class DebugEditor extends JavaEditor implements ActionListener { protected VariableInspector vi; // the variable inspector frame protected TextArea ta; // the text area + + protected ErrorBar errorBar; + /** + * Show Console button + */ + protected XQConsoleToggle btnShowConsole; + + /** + * Show Problems button + */ + protected XQConsoleToggle btnShowErrors; + + /** + * Scroll pane for Error Table + */ + protected JScrollPane errorTableScrollPane; + + /** + * Panel with card layout which contains the p5 console and Error Table + * panes + */ + protected JPanel consoleProblemsPane; + + protected XQErrorTable errorTable; + + /** + * Enable/Disable compilation checking + */ + protected boolean compilationCheckEnabled = true; + public DebugEditor(Base base, String path, EditorState state, Mode mode) { super(base, path, state, mode); @@ -121,6 +165,63 @@ public class DebugEditor extends JavaEditor implements ActionListener { dbg.setBreakpoint(lineID); } getSketch().setModified(false); // setting breakpoints will flag sketch as modified, so override this here + + checkForJavaTabs(); + initializeErrorChecker(); + ta.setECSandThemeforTextArea(errorCheckerService, dmode); + addXQModeUI(); + } + + private void addXQModeUI(){ + + // Adding ErrorBar + JPanel textAndError = new JPanel(); + Box box = (Box) textarea.getParent(); + box.remove(2); // Remove textArea from it's container, i.e Box + textAndError.setLayout(new BorderLayout()); + errorBar = new ErrorBar(this, textarea.getMinimumSize().height, dmode); + textAndError.add(errorBar, BorderLayout.EAST); + textarea.setBounds(0, 0, errorBar.getX() - 1, textarea.getHeight()); + textAndError.add(textarea); + box.add(textAndError); + + // Adding Error Table in a scroll pane + errorTableScrollPane = new JScrollPane(); + errorTable = new XQErrorTable(errorCheckerService); + // errorTableScrollPane.setBorder(new EmptyBorder(2, 2, 2, 2)); + errorTableScrollPane.setBorder(new EtchedBorder()); + errorTableScrollPane.setViewportView(errorTable); + + // Adding toggle console button + consolePanel.remove(2); + JPanel lineStatusPanel = new JPanel(); + lineStatusPanel.setLayout(new BorderLayout()); + btnShowConsole = new XQConsoleToggle(this, + XQConsoleToggle.text[0], lineStatus.getHeight()); + btnShowErrors = new XQConsoleToggle(this, + XQConsoleToggle.text[1], lineStatus.getHeight()); + btnShowConsole.addMouseListener(btnShowConsole); + + // lineStatusPanel.add(btnShowConsole, BorderLayout.EAST); + // lineStatusPanel.add(btnShowErrors); + btnShowErrors.addMouseListener(btnShowErrors); + + JPanel toggleButtonPanel = new JPanel(new BorderLayout()); + toggleButtonPanel.add(btnShowConsole, BorderLayout.EAST); + toggleButtonPanel.add(btnShowErrors, BorderLayout.WEST); + lineStatusPanel.add(toggleButtonPanel, BorderLayout.EAST); + lineStatus.setBounds(0, 0, toggleButtonPanel.getX() - 1, + toggleButtonPanel.getHeight()); + lineStatusPanel.add(lineStatus); + consolePanel.add(lineStatusPanel, BorderLayout.SOUTH); + lineStatusPanel.repaint(); + + // Adding JPanel with CardLayout for Console/Problems Toggle + consolePanel.remove(1); + consoleProblemsPane = new JPanel(new CardLayout()); + consoleProblemsPane.add(errorTableScrollPane, XQConsoleToggle.text[1]); + consoleProblemsPane.add(console, XQConsoleToggle.text[0]); + consolePanel.add(consoleProblemsPane, BorderLayout.CENTER); } // /** @@ -244,8 +345,166 @@ public class DebugEditor extends JavaEditor implements ActionListener { debugMenu.add(printThreads); debugMenu.addSeparator(); debugMenu.add(toggleVariableInspectorMenuItem); + debugMenu.addSeparator(); + + // XQMode menu items + + JCheckBoxMenuItem item; + final DebugEditor thisEditor = this; + item = new JCheckBoxMenuItem("Error Checker Enabled"); + item.setSelected(true); + item.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + if (!((JCheckBoxMenuItem) e.getSource()).isSelected()) { + // unticked Menu Item + errorCheckerService.pauseThread(); + System.out.println(thisEditor.getSketch().getName() + + " - Error Checker paused."); + errorBar.errorPoints.clear(); + errorCheckerService.problemsList.clear(); + errorCheckerService.updateErrorTable(); + errorCheckerService.updateEditorStatus(); + getTextArea().repaint(); + } else { + errorCheckerService.resumeThread(); + System.out.println(thisEditor.getSketch().getName() + + " - Error Checker resumed."); + } + } + }); + debugMenu.add(item); + + problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); + // problemWindowMenuCB.setSelected(true); + problemWindowMenuCB.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (errorCheckerService.errorWindow == null) + return; + errorCheckerService.errorWindow + .setVisible(((JCheckBoxMenuItem) e.getSource()) + .isSelected()); + // switch to console, now that Error Window is open + toggleView(XQConsoleToggle.text[0]); + } + }); + debugMenu.add(problemWindowMenuCB); + + showWarnings = new JCheckBoxMenuItem("Warnings Enabled"); + showWarnings.setSelected(true); + showWarnings.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + errorCheckerService.warningsEnabled = ((JCheckBoxMenuItem) e + .getSource()).isSelected(); + } + }); + debugMenu.add(showWarnings); + + return debugMenu; } + + /** + * Show warnings menu item + */ + protected JCheckBoxMenuItem showWarnings; + + /** + * Check box menu item for show/hide Problem Window + */ + public JCheckBoxMenuItem problemWindowMenuCB; + + + public JMenu buildXQModeMenu() { + + // Enable Error Checker - CB + // Show/Hide Problem Window - CB + // Show Warnings - CB + JMenu menu = new JMenu("XQMode"); + JCheckBoxMenuItem item; + final DebugEditor thisEditor = this; + item = new JCheckBoxMenuItem("Error Checker Enabled"); + item.setSelected(true); + item.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + if (!((JCheckBoxMenuItem) e.getSource()).isSelected()) { + // unticked Menu Item + errorCheckerService.pauseThread(); + System.out.println(thisEditor.getSketch().getName() + + " - Error Checker paused."); + errorBar.errorPoints.clear(); + errorCheckerService.problemsList.clear(); + errorCheckerService.updateErrorTable(); + errorCheckerService.updateEditorStatus(); + getTextArea().repaint(); + } else { + errorCheckerService.resumeThread(); + System.out.println(thisEditor.getSketch().getName() + + " - Error Checker resumed."); + } + } + }); + menu.add(item); + + problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); + // problemWindowMenuCB.setSelected(true); + problemWindowMenuCB.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (errorCheckerService.errorWindow == null) + return; + errorCheckerService.errorWindow + .setVisible(((JCheckBoxMenuItem) e.getSource()) + .isSelected()); + // switch to console, now that Error Window is open + toggleView(XQConsoleToggle.text[0]); + } + }); + menu.add(problemWindowMenuCB); + + showWarnings = new JCheckBoxMenuItem("Warnings Enabled"); + showWarnings.setSelected(true); + showWarnings.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + errorCheckerService.warningsEnabled = ((JCheckBoxMenuItem) e + .getSource()).isSelected(); + } + }); + menu.add(showWarnings); + + menu.addSeparator(); + + JMenuItem item2 = new JMenuItem("XQMode Wiki"); + item2.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Base.openURL("https://github.com/Manindra29/XQMode/wiki"); + } + }); + menu.add(item2); + + item2 = new JMenuItem("XQMode on Github"); + item2.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Base.openURL("https://github.com/Manindra29/XQMode"); + } + }); + menu.add(item2); + return menu; + } @Override public JMenu buildModeMenu() { @@ -892,4 +1151,66 @@ public class DebugEditor extends JavaEditor implements ActionListener { public void statusHalted() { statusNotice("Debugger halted."); } + + ErrorCheckerService errorCheckerService; + + /** + * Initializes and starts Error Checker Service + */ + private void initializeErrorChecker() { + Thread errorCheckerThread = null; + + if (errorCheckerThread == null) { + errorCheckerService = new ErrorCheckerService(this); + errorCheckerThread = new Thread(errorCheckerService); + try { + errorCheckerThread.start(); + } catch (Exception e) { + System.err + .println("Error Checker Service not initialized [XQEditor]: " + + e); + // e.printStackTrace(); + } + // System.out.println("Error Checker Service initialized."); + } + + } + + public void updateErrorBar(ArrayList problems) { + errorBar.updateErrorPoints(problems); + } + + /** + * Toggle between Console and Errors List + * + * @param buttonName + * - Button Label + */ + public void toggleView(String buttonName) { + CardLayout cl = (CardLayout) consoleProblemsPane.getLayout(); + cl.show(consoleProblemsPane, buttonName); + } + + synchronized public boolean updateTable(final TableModel tableModel) { + return errorTable.updateTable(tableModel); + } + + /** + * Checks if the sketch contains java tabs. If it does, XQMode ain't built + * for it, yet. Also, user should really start looking at Eclipse. Disable + * compilation check. + */ + private void checkForJavaTabs() { + for (int i = 0; i < this.getSketch().getCodeCount(); i++) { + if (this.getSketch().getCode(i).getExtension().equals("java")) { + compilationCheckEnabled = false; + JOptionPane.showMessageDialog(new Frame(), this + .getSketch().getName() + + " contains .java tabs. Live compilation error checking isn't " + + "supported for java tabs. Only " + + "syntax errors will be reported for .pde tabs."); + break; + } + } + } } diff --git a/app/src/processing/mode/java2/ErrorBar.java b/app/src/processing/mode/java2/ErrorBar.java new file mode 100755 index 000000000..731107878 --- /dev/null +++ b/app/src/processing/mode/java2/ErrorBar.java @@ -0,0 +1,373 @@ +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.java2; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; + +import javax.swing.JPanel; +import javax.swing.SwingWorker; + +import processing.app.Base; +import processing.app.SketchCode; + +/** + * The bar on the left of the text area which displays all errors as rectangles.
+ *
+ * All errors and warnings of a sketch are drawn on the bar, clicking on one, + * scrolls to the tab and location. Error messages displayed on hover. Markers + * are not in sync with the error line. Similar to eclipse's right error bar + * which displays the overall errors in a document + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class ErrorBar extends JPanel { + /** + * Preferred height of the component + */ + protected int preferredHeight; + + /** + * Preferred height of the component + */ + protected int preferredWidth = 12; + + /** + * Height of marker + */ + public static final int errorMarkerHeight = 4; + + /** + * Color of Error Marker + */ + public Color errorColor = new Color(0xED2630); + + /** + * Color of Warning Marker + */ + public Color warningColor = new Color(0xFFC30E); + + /** + * Background color of the component + */ + public Color backgroundColor = new Color(0x2C343D); + + protected DebugEditor editor; + + protected ErrorCheckerService errorCheckerService; + + /** + * Stores error markers displayed PER TAB along the error bar. + */ + protected ArrayList errorPoints = new ArrayList(); + + protected ArrayList errorPointsOld = new ArrayList(); + + public void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(backgroundColor); + g.fillRect(0, 0, getWidth(), getHeight()); + + for (ErrorMarker emarker : errorPoints) { + if (emarker.type == ErrorMarker.Error) { + g.setColor(errorColor); + } + else { + g.setColor(warningColor); + } + g.fillRect(2, emarker.y, (getWidth() - 3), errorMarkerHeight); + } + } + + public Dimension getPreferredSize() { + return new Dimension(preferredWidth, preferredHeight); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public ErrorBar(DebugEditor editor, int height, DebugMode mode) { + this.editor = editor; + this.preferredHeight = height; + this.errorCheckerService = editor.errorCheckerService; + errorColor = mode.getThemeColor("errorbar.errorcolor", errorColor); + warningColor = mode.getThemeColor("errorbar.warningcolor", + warningColor); + backgroundColor = mode.getThemeColor("errorbar.backgroundcolor", + backgroundColor); + addListeners(); + } + + /** + * Update error markers in the error bar. + * + * @param problems + * - List of problems. + */ + synchronized public void updateErrorPoints(final ArrayList problems) { + + // NOTE TO SELF: ErrorMarkers are calculated for the present tab only + // Error Marker index in the arraylist is LOCALIZED for current tab. + + final int fheight = this.getHeight(); + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + return null; + } + + protected void done() { + int bigCount = 0; + int totalLines = 0; + int currentTab = 0; + for (SketchCode sc : editor.getSketch().getCode()) { + if (sc.isExtension("pde")) { + sc.setPreprocOffset(bigCount); + + try { + if (editor.getSketch().getCurrentCode().equals(sc)) { + // Adding + 1 to len because \n gets appended for each + // sketchcode extracted during processPDECode() + totalLines = Base.countLines(sc.getDocument().getText( + 0, sc.getDocument().getLength())) + 1; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + currentTab++; + } + // System.out.println("Total lines: " + totalLines); + + errorPointsOld.clear(); + for (ErrorMarker marker : errorPoints) { + errorPointsOld.add(marker); + } + errorPoints.clear(); + + // Each problem.getSourceLine() will have an extra line added because of + // class declaration in the beginning + for (Problem problem : problems) { + if (problem.tabIndex == currentTab) { + // Ratio of error line to total lines + float y = problem.lineNumber / ((float) totalLines); + // Ratio multiplied by height of the error bar + y *= fheight - 15; // -15 is just a vertical offset + errorPoints.add(new ErrorMarker(problem, (int) y, problem + .isError() ? ErrorMarker.Error : ErrorMarker.Warning)); + // System.out.println("Y: " + y); + } + } + + repaint(); + } + }; + + try { + worker.execute(); // I eat concurrency bugs for breakfast. + } catch (Exception exp) { + System.out.println("Errorbar update markers is slacking." + + exp.getMessage()); + // e.printStackTrace(); + } + } + + /** + * Check if new errors have popped up in the sketch since the last check + * + * @return true - if errors have changed + */ + public boolean errorPointsChanged() { + if (errorPointsOld.size() != errorPoints.size()) { + editor.getTextArea().repaint(); + // System.out.println("2 Repaint " + System.currentTimeMillis()); + return true; + } + + else { + for (int i = 0; i < errorPoints.size(); i++) { + if (errorPoints.get(i).y != errorPointsOld.get(i).y) { + editor.getTextArea().repaint(); + // System.out.println("3 Repaint " + + // System.currentTimeMillis()); + return true; + } + } + } + return false; + } + + /** + * Add various mouse listeners. + */ + protected void addListeners() { + + this.addMouseListener(new MouseAdapter() { + + // Find out which error/warning the user has clicked + // and then scroll to that + @SuppressWarnings("rawtypes") + @Override + public void mouseClicked(final MouseEvent e) { + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + return null; + } + + protected void done() { + for (ErrorMarker eMarker : errorPoints) { + // -2 and +2 are extra allowance, clicks in the + // vicinity of the markers register that way + if (e.getY() >= eMarker.y - 2 + && e.getY() <= eMarker.y + 2 + + errorMarkerHeight) { + int currentTabErrorIndex = errorPoints + .indexOf(eMarker); + // System.out.println("Index: " + + // currentTabErrorIndex); + int currentTab = editor.getSketch() + .getCodeIndex( + editor.getSketch() + .getCurrentCode()); + + int totalErrorIndex = currentTabErrorIndex; + + for (int i = 0; i < errorCheckerService.problemsList + .size(); i++) { + Problem p = errorCheckerService.problemsList + .get(i); + if (p.tabIndex < currentTab) + totalErrorIndex++; + if (p.tabIndex == currentTab) + break; + } + errorCheckerService + .scrollToErrorLine(totalErrorIndex); + } + } + + } + }; + + try { + worker.execute(); + } catch (Exception exp) { + System.out.println("Errorbar mouseClicked is slacking." + + exp.getMessage()); + // e.printStackTrace(); + } + + } + }); + + // Tooltip on hover + this.addMouseMotionListener(new MouseMotionListener() { + + @SuppressWarnings("rawtypes") + @Override + public void mouseMoved(final MouseEvent e) { + // System.out.println(e); + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + return null; + } + + protected void done() { + + for (ErrorMarker eMarker : errorPoints) { + if (e.getY() >= eMarker.y - 2 + && e.getY() <= eMarker.y + 2 + + errorMarkerHeight) { + // System.out.println("Index: " + + // errorPoints.indexOf(y)); + int currentTab = editor.getSketch() + .getCodeIndex( + editor.getSketch() + .getCurrentCode()); + int currentTabErrorCount = 0; + + for (int i = 0; i < errorPoints.size(); i++) { + Problem p = errorPoints.get(i).problem; + if (p.tabIndex == currentTab) { + if (currentTabErrorCount == errorPoints + .indexOf(eMarker)) { + // System.out.println("Roger that."); + String msg = (p.isError() ? "Error: " + : "Warning: ") + + p.message; + setToolTipText(msg); + setCursor(Cursor + .getPredefinedCursor(Cursor.HAND_CURSOR)); + return; + } else { + currentTabErrorCount++; + // System.out.println("Still looking.."); + } + } + + } + } + // Reset cursor and tooltip + else { + setToolTipText(""); + setCursor(Cursor + .getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + + } + }; + try { + worker.execute(); + } catch (Exception exp) { + System.out + .println("Errorbar mousemoved Worker is slacking." + + exp.getMessage()); + // e.printStackTrace(); + } + } + + @Override + public void mouseDragged(MouseEvent arg0) { + + } + }); + + } + +} diff --git a/app/src/processing/mode/java2/ErrorCheckerService.java b/app/src/processing/mode/java2/ErrorCheckerService.java new file mode 100755 index 000000000..f8385b296 --- /dev/null +++ b/app/src/processing/mode/java2/ErrorCheckerService.java @@ -0,0 +1,1097 @@ +package processing.mode.java2; + +import java.awt.EventQueue; +import java.io.File; +import java.io.FileFilter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.table.DefaultTableModel; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.compiler.IProblem; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; + +import processing.app.Base; +import processing.app.Library; +import processing.app.SketchCode; +import processing.core.PApplet; + +public class ErrorCheckerService implements Runnable{ + + private DebugEditor editor; + /** + * Error check happens every sleepTime milliseconds + */ + public static final int sleepTime = 1000; + + /** + * The amazing eclipse ast parser + */ + private ASTParser parser; + + /** + * Used to indirectly stop the Error Checker Thread + */ + public boolean stopThread = false; + + /** + * If true, Error Checking is paused. Calls to checkCode() become useless. + */ + private boolean pauseThread = false; + + protected ErrorWindow errorWindow; +// protected ErrorBar errorBar; + /** + * IProblem[] returned by parser stored in here + */ + private IProblem[] problems; + + /** + * Class name of current sketch + */ + protected String className; + + /** + * Source code of current sketch + */ + protected String sourceCode; + + /** + * URLs of extra imports jar files stored here. + */ + protected URL[] classpath; + + /** + * P5 Preproc offset + */ + private int scPreProcOffset = 0; + + /** + * Stores all Problems in the sketch + */ + public ArrayList problemsList; + + /** + * How many lines are present till the initial class declaration? In static + * mode, this would include imports, class declaration and setup + * declaration. In nomral mode, this would include imports, class + * declaration only. It's fate is decided inside preprocessCode() + */ + public int mainClassOffset; + + /** + * Is the sketch running in static mode or active mode? + */ + public boolean staticMode = false; + + /** + * Compilation Unit for current sketch + */ + protected CompilationUnit cu; + + /** + * If true, compilation checker will be reloaded with updated classpath + * items. + */ + private boolean loadCompClass = true; + + /** + * Compiler Checker class. Note that methods for compilation checking are + * called from the compilationChecker object, not from this + */ + protected Class checkerClass; + + /** + * Compilation Checker object. + */ + protected Object compilationChecker; + + + /** + * List of jar files to be present in compilation checker's classpath + */ + protected ArrayList classpathJars; + + /** + * Timestamp - for measuring total overhead + */ + private long lastTimeStamp = System.currentTimeMillis(); + + /** + * Used for displaying the rotating slash on the Problem Window title bar + */ + private String[] slashAnimation = { "|", "/", "--", "\\", "|", "/", "--", + "\\" }; + private int slashAnimationIndex = 0; + + /** + * Used to detect if the current tab index has changed and thus repaint the + * textarea. + */ + public int currentTab = 0, lastTab = 0; + + /** + * Stores the current import statements in the program. Used to compare for + * changed import statements and update classpath if needed. + */ + protected ArrayList programImports; + + /** + * List of imports when sketch was last checked. Used for checking for + * changed imports + */ + protected ArrayList previousImports = new ArrayList(); + + /** + * Teh Preprocessor + */ + protected XQPreprocessor xqpreproc; + + /** + * Regexp for import statements. (Used from Processing source) + */ + final public String importRegexp = "(?:^|;)\\s*(import\\s+)((?:static\\s+)?\\S+)(\\s*;)"; + + /** + * Regexp for function declarations. (Used from Processing source) + */ + final Pattern FUNCTION_DECL = Pattern + .compile("(^|;)\\s*((public|private|protected|final|static)\\s+)*" + + "(void|int|float|double|String|char|byte)" + + "(\\s*\\[\\s*\\])?\\s+[a-zA-Z0-9]+\\s*\\(", Pattern.MULTILINE); + + public ErrorCheckerService(DebugEditor debugEditor) { + this.editor = debugEditor; + initParser(); + initializeErrorWindow(); + xqpreproc = new XQPreprocessor(); + } + + /** + * Initializes ASTParser + */ + private void initParser() { + try { + parser = ASTParser.newParser(AST.JLS4); + } catch (Exception e) { + System.err.println("XQMode initialization failed. " + + "Are you running the right version of Processing? "); + pauseThread(); + } catch (Error e) { + System.err.println("XQMode initialization failed. "); + e.printStackTrace(); + pauseThread(); + } + } + + /** + * Initialiazes the Error Window + */ + public void initializeErrorWindow() { + + if (editor == null) { + return; + } + + if (errorWindow != null) { + return; + } + + final ErrorCheckerService thisService = this; + final DebugEditor thisEditor = editor; + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + errorWindow = new ErrorWindow(thisEditor, thisService); + // errorWindow.setVisible(true); + editor.toFront(); + errorWindow.errorTable.setFocusable(false); + editor.setSelection(0, 0); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + public void run() { + stopThread = false; + + while (!stopThread) { + try { + // Take a nap. + Thread.sleep(sleepTime); + } catch (Exception e) { + System.out.println("Oops! [ErrorCheckerThreaded]: " + e); + // e.printStackTrace(); + } + + if (pauseThread) + continue; + + // Check every x seconds + checkCode(); + + } + } + + private boolean checkCode() { + + lastTimeStamp = System.currentTimeMillis(); + try { + sourceCode = preprocessCode(editor.getSketch().getMainProgram()); + + syntaxCheck(); + + // No syntax errors, proceed for compilation check, Stage 2. + if (problems.length == 0 && editor.compilationCheckEnabled) { //TODO: && editor.compilationCheckEnabled condition + sourceCode = xqpreproc.doYourThing(sourceCode, programImports); + prepareCompilerClasspath(); + mainClassOffset = xqpreproc.mainClassOffset; // tiny, but + // significant + if (staticMode) + mainClassOffset++; // Extra line for setup() decl. +// System.out.println(sourceCode); +// System.out.println("--------------------------"); + compileCheck(); + } + + + updateErrorTable(); + editor.updateErrorBar(problemsList); + updateEditorStatus(); + updateTextAreaPainter(); + return true; + + } catch (Exception e) { + System.out.println("Oops! [ErrorCheckerService.checkCode]: " + e); + e.printStackTrace(); + } + return false; + } + + private void syntaxCheck() { + parser.setSource(sourceCode.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + @SuppressWarnings("unchecked") + Map options = JavaCore.getOptions(); + + JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_6); + parser.setCompilerOptions(options); + cu = (CompilationUnit) parser.createAST(null); + + // Store errors returned by the ast parser + problems = cu.getProblems(); + // System.out.println("Problem Count: " + problems.length); + // Populate the probList + problemsList = new ArrayList(); + for (int i = 0; i < problems.length; i++) { + int a[] = calculateTabIndexAndLineNumber(problems[i]); + Problem p = new Problem(problems[i], a[0], a[1]); + problemsList.add(p); +// System.out.println(p.toString()); + } + } + + private void compileCheck() { + + // Currently (Sept, 2012) I'm using Java's reflection api to load the + // CompilationChecker class(from CompilationChecker.jar) that houses the + // Eclispe JDT compiler and call its getErrorsAsObj method to obtain + // errors. This way, I'm able to add the paths of contributed libraries + // to the classpath of CompilationChecker, dynamically. + + try { + + // NOTE TO SELF: If classpath contains null Strings + // URLClassLoader gets angry. Drops NPE bombs. + + // If imports have changed, reload classes with new classpath. + if (loadCompClass) { + + // if (classpathJars.size() > 0) + // System.out + // .println("XQMode: Loading contributed libraries referenced by import statements."); + + File f = new File("modes" + + File.separator + + "java2" + + File.separator + "mode"); + + FileFilter fileFilter = new FileFilter() { + public boolean accept(File file) { + return (file.getName().endsWith(".jar") && !file + .getName().startsWith("XQMode")); + } + }; + + File[] jarFiles = f.listFiles(fileFilter); + for (File jarFile : jarFiles) { + classpathJars.add(jarFile.toURI().toURL()); + } + + classpath = new URL[classpathJars.size()]; // + 1 for + // Compilation + // Checker class + for (int i = 0; i < classpathJars.size(); i++) { + classpath[i] = classpathJars.get(i); + } + + // System.out.println("CP Len -- " + classpath.length); + URLClassLoader classLoader = new URLClassLoader(classpath); + // System.out.println("1."); + checkerClass = Class.forName("CompilationChecker", true, + classLoader); + // System.out.println("2."); + compilationChecker = checkerClass.newInstance(); + loadCompClass = false; + } + + if (compilerSettings == null) { + prepareCompilerSetting(); + } + Method getErrors = checkerClass.getMethod("getErrorsAsObjArr", + new Class[] { String.class, String.class, Map.class }); + + Object[][] errorList = (Object[][]) getErrors + .invoke(compilationChecker, className, sourceCode, + compilerSettings); + + if (errorList == null) { + return; + } + + problems = new DefaultProblem[errorList.length]; + + for (int i = 0; i < errorList.length; i++) { + + // for (int j = 0; j < errorList[i].length; j++) + // System.out.print(errorList[i][j] + ", "); + + problems[i] = new DefaultProblem((char[]) errorList[i][0], + (String) errorList[i][1], + ((Integer) errorList[i][2]).intValue(), + (String[]) errorList[i][3], + ((Integer) errorList[i][4]).intValue(), + ((Integer) errorList[i][5]).intValue(), + ((Integer) errorList[i][6]).intValue(), + ((Integer) errorList[i][7]).intValue(), 0); + + // System.out + // .println("ECS: " + problems[i].getMessage() + "," + // + problems[i].isError() + "," + // + problems[i].isWarning()); + + IProblem problem = problems[i]; + + int a[] = calculateTabIndexAndLineNumber(problem); + Problem p = new Problem(problem, a[0], a[1]); + if ((Boolean) errorList[i][8]) { + p.setType(Problem.ERROR); + } + + if ((Boolean) errorList[i][9]) { + p.setType(Problem.WARNING); + } + + // If warnings are disabled, skip 'em + if (p.isWarning() && !warningsEnabled) { + continue; + } + problemsList.add(p); + } + + } catch (ClassNotFoundException e) { + System.err.println("Compiltation Checker files couldn't be found! " + + e + " compileCheck() problem."); + stopThread(); + } catch (MalformedURLException e) { + System.err.println("Compiltation Checker files couldn't be found! " + + e + " compileCheck() problem."); + stopThread(); + } catch (Exception e) { + System.err.println("compileCheck() problem." + e); + e.printStackTrace(); + stopThread(); + } catch (NoClassDefFoundError e) { + System.err + .println(e + + " compileCheck() problem. Somebody tried to mess with XQMode files."); + stopThread(); + } + // System.out.println("Compilecheck, Done."); + } + + /** + * Processes import statements to obtain classpaths of contributed + * libraries. This would be needed for compilation check. Also, adds + * stuff(jar files, class files, candy) from the code folder. And it looks + * messed up. + * + */ + private void prepareCompilerClasspath() { + if (!loadCompClass) + return; + // System.out.println("1.."); + classpathJars = new ArrayList(); + String entry = ""; + boolean codeFolderChecked = false; + for (ImportStatement impstat : programImports) { + String item = impstat.importName; + int dot = item.lastIndexOf('.'); + entry = (dot == -1) ? item : item.substring(0, dot); + + entry = entry.substring(6).trim(); + // System.out.println("Entry--" + entry); + if (ignorableImport(entry)) { + // System.out.println("Ignoring: " + entry); + continue; + } + Library library = null; + + // Try to get the library classpath and add it to the list + try { + library = editor.getMode().getLibrary(entry); + // System.out.println("lib->" + library.getClassPath() + "<-"); + String libraryPath[] = PApplet.split(library.getClassPath() + .substring(1).trim(), File.pathSeparatorChar); + for (int i = 0; i < libraryPath.length; i++) { + // System.out.println(entry + " ::" + // + new File(libraryPath[i]).toURI().toURL()); + classpathJars.add(new File(libraryPath[i]).toURI().toURL()); + } + // System.out.println("-- "); + // classpath[count] = (new File(library.getClassPath() + // .substring(1))).toURI().toURL(); + // System.out.println(" found "); + // System.out.println(library.getClassPath().substring(1)); + } catch (Exception e) { + if (library == null && !codeFolderChecked) { + // System.out.println(1); + // Look around in the code folder for jar files + if (editor.getSketch().hasCodeFolder()) { + File codeFolder = editor.getSketch().getCodeFolder(); + + // get a list of .jar files in the "code" folder + // (class files in subfolders should also be picked up) + String codeFolderClassPath = Base + .contentsToClassPath(codeFolder); + codeFolderChecked = true; + if (codeFolderClassPath.equalsIgnoreCase("")) { + System.err.println("XQMODE: Yikes! Can't find \"" + + entry + + "\" library! Line: " + + impstat.lineNumber + + " in tab: " + + editor.getSketch().getCode(impstat.tab) + .getPrettyName()); + System.out + .println("Please make sure that the library is present in /libraries folder or in the code folder of your sketch"); + + } + String codeFolderPath[] = PApplet.split( + codeFolderClassPath.substring(1).trim(), + File.pathSeparatorChar); + try { + for (int i = 0; i < codeFolderPath.length; i++) { + classpathJars.add(new File(codeFolderPath[i]) + .toURI().toURL()); + } + + } catch (Exception e2) { + System.out + .println("Yikes! codefolder, prepareImports(): " + + e2); + } + } else { + System.err.println("XQMODE: Yikes! Can't find \"" + + entry + + "\" library! Line: " + + impstat.lineNumber + + " in tab: " + + editor.getSketch().getCode(impstat.tab) + .getPrettyName()); + System.out + .println("Please make sure that the library is present in /libraries folder or in the code folder of your sketch"); + } + + } else { + System.err + .println("Yikes! There was some problem in prepareImports(): " + + e); + System.err.println("I was processing: " + entry); + + // e.printStackTrace(); + } + } + + } + + } + + /** + * Ignore processing packages, java.*.*. etc. + * + * @param packageName + * @return boolean + */ + protected boolean ignorableImport(String packageName) { + // packageName.startsWith("processing.") + // || + if (packageName.startsWith("java.") || packageName.startsWith("javax.")) { + return true; + } + return false; + } + + /** + * Various option for JDT Compiler + */ + @SuppressWarnings("rawtypes") + protected Map compilerSettings; + + /** + * Enable/Disable warnings from being shown + */ + public boolean warningsEnabled = true; + + /** + * Sets compiler options for JDT Compiler + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void prepareCompilerSetting() { + compilerSettings = new HashMap(); + + compilerSettings.put(CompilerOptions.OPTION_LineNumberAttribute, + CompilerOptions.GENERATE); + compilerSettings.put(CompilerOptions.OPTION_SourceFileAttribute, + CompilerOptions.GENERATE); + compilerSettings.put(CompilerOptions.OPTION_Source, + CompilerOptions.VERSION_1_6); + compilerSettings.put(CompilerOptions.OPTION_ReportUnusedImport, + CompilerOptions.IGNORE); + compilerSettings.put(CompilerOptions.OPTION_ReportMissingSerialVersion, + CompilerOptions.IGNORE); + compilerSettings.put(CompilerOptions.OPTION_ReportRawTypeReference, + CompilerOptions.IGNORE); + compilerSettings.put( + CompilerOptions.OPTION_ReportUncheckedTypeOperation, + CompilerOptions.IGNORE); + } + + + /** + * Updates the error table in the Error Window. + */ + synchronized public void updateErrorTable() { + + try { + String[][] errorData = new String[problemsList.size()][3]; + for (int i = 0; i < problemsList.size(); i++) { + errorData[i][0] = problemsList.get(i).message; + errorData[i][1] = editor.getSketch() + .getCode(problemsList.get(i).tabIndex).getPrettyName(); + errorData[i][2] = problemsList.get(i).lineNumber + ""; + } + + if (errorWindow != null) { + DefaultTableModel tm = new DefaultTableModel(errorData, + XQErrorTable.columnNames); + if (errorWindow.isVisible()) { + errorWindow.updateTable(tm); + } + + // Update error table in the editor + editor.updateTable(tm); + + // A rotating slash animation on the title bar to show + // that error checker thread is running + + slashAnimationIndex++; + if (slashAnimationIndex == slashAnimation.length) { + slashAnimationIndex = 0; + } + if (editor != null) { + String info = slashAnimation[slashAnimationIndex] + " T:" + + (System.currentTimeMillis() - lastTimeStamp) + + "ms"; + errorWindow.setTitle("Problems - " + + editor.getSketch().getName() + " " + info); + } + } + + } catch (Exception e) { + System.out.println("Exception at updateErrorTable() " + e); + e.printStackTrace(); + stopThread(); + } + + } + + /** + * Repaints the textarea if required + */ + public void updateTextAreaPainter() { + editor.getTextArea().repaint(); + currentTab = editor.getSketch().getCodeIndex( + editor.getSketch().getCurrentCode()); + if (currentTab != lastTab) { + lastTab = currentTab; + // editor.getTextArea().repaint(); + // System.out.println("1 Repaint " + System.currentTimeMillis()); + return; + } + +// TODO: if (errorBar.errorPointsChanged()) +// editor.getTextArea().repaint(); + + } + + /** + * Updates editor status bar, depending on whether the caret is on an error + * line or not + */ + public void updateEditorStatus() { + // editor.statusNotice("Position: " + + // editor.getTextArea().getCaretLine()); + boolean notFound = true; + for (ErrorMarker emarker : editor.errorBar.errorPoints) { + if (emarker.problem.lineNumber == editor.getTextArea() + .getCaretLine() + 1) { + if (emarker.type == ErrorMarker.Warning) { + editor.statusNotice(emarker.problem.message); + } + else { + editor.statusError(emarker.problem.message); + } + return; + } + } + if (notFound) { + editor.statusEmpty(); + } + } + + /** + * Calculates the tab number and line number of the error in that particular + * tab. Provides mapping between pure java and pde code. + * + * @param problem + * - IProblem + * @return int[0] - tab number, int[1] - line number + */ + public int[] calculateTabIndexAndLineNumber(IProblem problem) { + // String[] lines = {};// = PApplet.split(sourceString, '\n'); + int codeIndex = 0; + int bigCount = 0; + + int x = problem.getSourceLineNumber() - mainClassOffset; + if (x < 0) { + // System.out.println("Negative line number " + // + problem.getSourceLineNumber() + " , offset " + // + mainClassOffset); + x = problem.getSourceLineNumber() - 2; // Another -1 for 0 index + if (x < programImports.size() && x >= 0) { + ImportStatement is = programImports.get(x); + // System.out.println(is.importName + ", " + is.tab + ", " + // + is.lineNumber); + return new int[] { is.tab, is.lineNumber }; + } else { + + // Some seriously ugly stray error, just can't find the source + // line! Simply return first line for first tab. + return new int[] { 0, 1 }; + } + + } + + try { + for (SketchCode sc : editor.getSketch().getCode()) { + if (sc.isExtension("pde")) { + sc.setPreprocOffset(bigCount); + int len = 0; + if (editor.getSketch().getCurrentCode().equals(sc)) { + len = Base.countLines(sc.getDocument().getText(0, + sc.getDocument().getLength())) + 1; + } else { + len = Base.countLines(sc.getProgram()) + 1; + } + + // System.out.println("x,len, CI: " + x + "," + len + "," + // + codeIndex); + + if (x >= len) { + + // We're in the last tab and the line count is greater + // than the no. + // of lines in the tab, + if (codeIndex >= editor.getSketch().getCodeCount() - 1) { + // System.out.println("Exceeds lc " + x + "," + len + // + problem.toString()); + // x = len + x = editor.getSketch().getCode(codeIndex) + .getLineCount(); + // TODO: Obtain line having last non-white space + // character in the code. + break; + } else { + x -= len; + codeIndex++; + } + } else { + + if (codeIndex >= editor.getSketch().getCodeCount()) { + codeIndex = editor.getSketch().getCodeCount() - 1; + } + break; + } + + } + bigCount += sc.getLineCount(); + } + } catch (Exception e) { + System.err + .println("Things got messed up in ErrorCheckerService.calculateTabIndexAndLineNumber()"); + } + + return new int[] { codeIndex, x }; + } + + /** + * Fetches code from the editor tabs and pre-processes it into parsable pure + * java source. And there's a difference between parsable and compilable. + * XQPrerocessor.java makes this code compilable.
+ * Handles:
  • Removal of import statements
  • Conversion of int(), + * char(), etc to (int)(), (char)(), etc.
  • Replacing '#' with 0xff for + * color representation
  • Converts all 'color' datatypes to int + * (experimental)
  • Appends class declaration statement after determining + * the mode the sketch is in - ACTIVE or STATIC + * + * @return String - Pure java representation of PDE code. Note that this + * code is not yet compile ready. + */ + + private String preprocessCode(String pdeCode) { + + programImports = new ArrayList(); + + StringBuffer rawCode = new StringBuffer(); + + try { + + for (SketchCode sc : editor.getSketch().getCode()) { + if (sc.isExtension("pde")) { + + sc.setPreprocOffset(scPreProcOffset); + + try { + + if (editor.getSketch().getCurrentCode().equals(sc)) { + + // rawCode.append(sc.getDocument().getText(0, + // sc.getDocument().getLength())); + rawCode.append(scrapImportStatements(sc.getDocument() + .getText(0, + sc.getDocument() + .getLength()), + editor.getSketch() + .getCodeIndex(sc))); + } else { + + // rawCode.append(sc.getProgram()); + rawCode.append(scrapImportStatements(sc.getProgram(), editor + .getSketch().getCodeIndex(sc))); + + } + rawCode.append('\n'); + } catch (Exception e) { + System.err.println("Exception in preprocessCode() - bigCode " + + e.toString()); + } + rawCode.append('\n'); + scPreProcOffset += sc.getLineCount(); + } + } + + } catch (Exception e) { + System.out.println("Exception in preprocessCode()"); + } + String sourceAlt = rawCode.toString(); + // Replace comments with whitespaces + // sourceAlt = scrubComments(sourceAlt); + + // Find all int(*), replace with PApplet.parseInt(*) + + // \bint\s*\(\s*\b , i.e all exclusive "int(" + + String dataTypeFunc[] = { "int", "char", "float", "boolean", "byte" }; + + for (String dataType : dataTypeFunc) { + String dataTypeRegexp = "\\b" + dataType + "\\s*\\("; + Pattern pattern = Pattern.compile(dataTypeRegexp); + Matcher matcher = pattern.matcher(sourceAlt); + + // while (matcher.find()) { + // System.out.print("Start index: " + matcher.start()); + // System.out.println(" End index: " + matcher.end() + " "); + // System.out.println("-->" + matcher.group() + "<--"); + // } + sourceAlt = matcher.replaceAll("PApplet.parse" + + Character.toUpperCase(dataType.charAt(0)) + + dataType.substring(1) + "("); + + } + + // Find all #[web color] and replace with 0xff[webcolor] + // Should be 6 digits only. + final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W"; + Pattern webPattern = Pattern.compile(webColorRegexp); + Matcher webMatcher = webPattern.matcher(sourceAlt); + while (webMatcher.find()) { + // System.out.println("Found at: " + webMatcher.start()); + String found = sourceAlt.substring(webMatcher.start(), + webMatcher.end()); + // System.out.println("-> " + found); + sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1)); + webMatcher = webPattern.matcher(sourceAlt); + } + + // Replace all color data types with int + // Regex, Y U SO powerful? + final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())"; + Pattern colorPattern = Pattern.compile(colorTypeRegex); + Matcher colorMatcher = colorPattern.matcher(sourceAlt); + sourceAlt = colorMatcher.replaceAll("int"); + + checkForChangedImports(); + + className = (editor == null) ? "DefaultClass" : editor.getSketch() + .getName(); + + // Check whether the code is being written in STATIC mode(no function + // declarations) - append class declaration and void setup() declaration + Matcher matcher = FUNCTION_DECL.matcher(sourceAlt); + if (!matcher.find()) { + sourceAlt = "public class " + className + " extends PApplet {\n" + + "public void setup() {\n" + sourceAlt + + "\nnoLoop();\n}\n" + "\n}\n"; + staticMode = true; + mainClassOffset = 2; + + } else { + sourceAlt = "public class " + className + " extends PApplet {\n" + + sourceAlt + "\n}"; + staticMode = false; + mainClassOffset = 1; + } + + // Handle unicode characters + sourceAlt = substituteUnicode(sourceAlt); + +// System.out.println("-->\n" + sourceAlt + "\n<--"); +// System.out.println("PDE code processed - " +// + editor.getSketch().getName()); + sourceCode = sourceAlt; + return sourceAlt; + + } + + /** + * Scrolls to the error source in code. And selects the line text. Used by + * XQErrorTable and ErrorBar + * + * @param errorIndex + * - index of error + */ + public void scrollToErrorLine(int errorIndex) { + if (editor == null) + return; + if (errorIndex < problemsList.size() && errorIndex >= 0) { + Problem p = problemsList.get(errorIndex); + try { + editor.toFront(); + editor.getSketch().setCurrentCode(p.tabIndex); + editor.getTextArea().scrollTo(p.lineNumber - 1, 0); + editor.setSelection(editor.getTextArea() + .getLineStartNonWhiteSpaceOffset(p.lineNumber - 1) + + editor.getTextArea().getLineText(p.lineNumber - 1) + .trim().length(), editor.getTextArea() + .getLineStartNonWhiteSpaceOffset(p.lineNumber - 1)); + editor.repaint(); + } catch (Exception e) { + System.err + .println(e + + " : Error while selecting text in scrollToErrorLine()"); + // e.printStackTrace(); + } + // System.out.println("---"); + + } + } + + /** + * Checks if import statements in the sketch have changed. If they have, + * compiler classpath needs to be updated. + */ + private void checkForChangedImports() { + // System.out.println("Imports: " + programImports.size() + + // " Prev Imp: " + // + previousImports.size()); + if (programImports.size() != previousImports.size()) { + // System.out.println(1); + loadCompClass = true; + previousImports = programImports; + } else { + for (int i = 0; i < programImports.size(); i++) { + if (!programImports.get(i).importName.equals(previousImports + .get(i).importName)) { + // System.out.println(2); + loadCompClass = true; + previousImports = programImports; + break; + } + } + } + // System.out.println("load..? " + loadCompClass); + } + + /** + * Removes import statements from tabSource, replaces each with white spaces + * and adds the import to the list of program imports + * + * @param tabProgram + * - Code in a tab + * @param tabNumber + * - index of the tab + * @return String - Tab code with imports replaced with white spaces + */ + private String scrapImportStatements(String tabProgram, int tabNumber) { + + String tabSource = new String(tabProgram); + do { + // System.out.println("-->\n" + sourceAlt + "\n<--"); + String[] pieces = PApplet.match(tabSource, importRegexp); + + // Stop the loop if we've removed all the import lines + if (pieces == null) { + break; + } + + String piece = pieces[1] + pieces[2] + pieces[3]; + int len = piece.length(); // how much to trim out + + // programImports.add(piece); // the package name + + // find index of this import in the program + int idx = tabSource.indexOf(piece); + // System.out.print("Import -> " + piece); + // System.out.println(" - " + // + Base.countLines(tabSource.substring(0, idx)) + " tab " + // + tabNumber); + programImports.add(new ImportStatement(piece, tabNumber, Base + .countLines(tabSource.substring(0, idx)))); + // Remove the import from the main program + // Substitue with white spaces + String whiteSpace = ""; + for (int j = 0; j < piece.length(); j++) { + whiteSpace += " "; + } + tabSource = tabSource.substring(0, idx) + whiteSpace + + tabSource.substring(idx + len); + + } while (true); + // System.out.println(tabSource); + return tabSource; + } + + /** + * Replaces non-ascii characters with their unicode escape sequences and + * stuff. Used as it is from + * processing.src.processing.mode.java.preproc.PdePreprocessor + * + * @param program + * - Input String containing non ascii characters + * @return String - Converted String + */ + public static String substituteUnicode(String program) { + // check for non-ascii chars (these will be/must be in unicode format) + char p[] = program.toCharArray(); + int unicodeCount = 0; + for (int i = 0; i < p.length; i++) { + if (p[i] > 127) { + unicodeCount++; + } + } + if (unicodeCount == 0) { + return program; + } + // if non-ascii chars are in there, convert to unicode escapes + // add unicodeCount * 5.. replacing each unicode char + // with six digit uXXXX sequence (xxxx is in hex) + // (except for nbsp chars which will be a replaced with a space) + int index = 0; + char p2[] = new char[p.length + unicodeCount * 5]; + for (int i = 0; i < p.length; i++) { + if (p[i] < 128) { + p2[index++] = p[i]; + } else if (p[i] == 160) { // unicode for non-breaking space + p2[index++] = ' '; + } else { + int c = p[i]; + p2[index++] = '\\'; + p2[index++] = 'u'; + char str[] = Integer.toHexString(c).toCharArray(); + // add leading zeros, so that the length is 4 + // for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0'; + for (int m = 0; m < 4 - str.length; m++) + p2[index++] = '0'; + System.arraycopy(str, 0, p2, index, str.length); + index += str.length; + } + } + return new String(p2, 0, index); + } + + /** + * Stops the Error Checker Service thread + */ + public void stopThread() { + stopThread = true; +// System.out.println(editor.getSketch().getName() +// + " - Error Checker stopped."); + } + + /** + * Pauses the Error Checker Service thread + */ + public void pauseThread() { + pauseThread = true; + } + + /** + * Resumes the Error Checker Service thread + */ + public void resumeThread() { + pauseThread = false; + } + + public DebugEditor getEditor() { + return editor; + } +} diff --git a/app/src/processing/mode/java2/ErrorMarker.java b/app/src/processing/mode/java2/ErrorMarker.java new file mode 100755 index 000000000..f87353ef1 --- /dev/null +++ b/app/src/processing/mode/java2/ErrorMarker.java @@ -0,0 +1,36 @@ +package processing.mode.java2; +/** + * Error markers displayed on the Error Bar. + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ + public class ErrorMarker { + /** + * y co-ordinate of the marker + */ + public int y; + /** + * Type of marker: Error or Warning? + */ + public int type = -1; + /** + * Error Type constant + */ + public static final int Error = 1; + /** + * Warning Type constant + */ + public static final int Warning = 2; + /** + * Problem that the error marker represents + * @see Problem + */ + public Problem problem; + + public ErrorMarker(Problem problem, int y, int type) { + this.problem = problem; + this.y = y; + this.type = type; + } + } \ No newline at end of file diff --git a/app/src/processing/mode/java2/ErrorWindow.java b/app/src/processing/mode/java2/ErrorWindow.java new file mode 100755 index 000000000..365022315 --- /dev/null +++ b/app/src/processing/mode/java2/ErrorWindow.java @@ -0,0 +1,374 @@ +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.java2; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableModel; + +import processing.app.Editor; +import processing.app.Toolkit; + +/** + * Error Window that displays a tablular list of errors. Clicking on an error + * scrolls to its location in the code. + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class ErrorWindow extends JFrame { + + private JPanel contentPane; + /** + * The table displaying the errors + */ + protected XQErrorTable errorTable; + /** + * Scroll pane that contains the Error Table + */ + protected JScrollPane scrollPane; + + protected DebugEditor thisEditor; + private JFrame thisErrorWindow; + + /** + * Handles the sticky Problem window + */ + private DockTool2Base Docker; + + protected ErrorCheckerService errorCheckerService; + + /** + * Preps up ErrorWindow + * + * @param editor + * - Editor + * @param ecs - ErrorCheckerService + */ + public ErrorWindow(DebugEditor editor, ErrorCheckerService ecs) { + thisErrorWindow = this; + errorCheckerService = ecs; + thisEditor = editor; + setTitle("Problems"); + prepareFrame(); + } + + /** + * Sets up ErrorWindow + */ + protected void prepareFrame() { + Toolkit.setIcon(this); + setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + // Default size: setBounds(100, 100, 458, 160); + setBounds(100, 100, 458, 160); // Yeah, I hardcode such things sometimes. Hate me. + + contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + setContentPane(contentPane); + contentPane.setLayout(new BorderLayout(0, 0)); + + scrollPane = new JScrollPane(); + contentPane.add(scrollPane); + + errorTable = new XQErrorTable(errorCheckerService); + scrollPane.setViewportView(errorTable); + + try { + Docker = new DockTool2Base(); + addListeners(); + } catch (Exception e) { + System.out.println("addListeners() acted silly."); + e.printStackTrace(); + } + + if (thisEditor != null) { + setLocation(new Point(thisEditor.getLocation().x + + thisEditor.getWidth(), thisEditor.getLocation().y)); + } + + } + + /** + * Updates the error table with new data(Table Model). Called from Error + * Checker Service. + * + * @param tableModel + * - Table Model + * @return True - If error table was updated successfully. + */ + synchronized public boolean updateTable(final TableModel tableModel) { + // XQErrorTable handles evrything now + return errorTable.updateTable(tableModel); + } + + /** + * Adds various listeners to components of EditorWindow and to the Editor + * window + */ + protected void addListeners() { + + if (thisErrorWindow == null) + System.out.println("ERW null"); + + thisErrorWindow.addComponentListener(new ComponentListener() { + + @Override + public void componentShown(ComponentEvent e) { + + } + + @Override + public void componentResized(ComponentEvent e) { + Docker.tryDocking(); + } + + @Override + public void componentMoved(ComponentEvent e) { + Docker.tryDocking(); + } + + @Override + public void componentHidden(ComponentEvent e) { + + } + }); + + thisErrorWindow.addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + thisEditor.problemWindowMenuCB.setSelected(false); + } + + @Override + public void windowDeiconified(WindowEvent e) { + thisEditor.setExtendedState(Frame.NORMAL); + } + + }); + + if (thisEditor == null) { + System.out.println("Editor null"); + return; + } + + thisEditor.addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + + } + + @Override + public void windowClosed(WindowEvent e) { + errorCheckerService.pauseThread(); + errorCheckerService.stopThread(); // Bye bye thread. + thisErrorWindow.dispose(); + } + + @Override + public void windowIconified(WindowEvent e) { + thisErrorWindow.setExtendedState(Frame.ICONIFIED); + } + + @Override + public void windowDeiconified(WindowEvent e) { + thisErrorWindow.setExtendedState(Frame.NORMAL); + } + + }); + + thisEditor.addComponentListener(new ComponentListener() { + + @Override + public void componentShown(ComponentEvent e) { + + } + + @Override + public void componentResized(ComponentEvent e) { + if (Docker.isDocked()) { + Docker.dock(); + } else { + Docker.tryDocking(); + } + } + + @Override + public void componentMoved(ComponentEvent e) { + + if (Docker.isDocked()) { + Docker.dock(); + } else { + Docker.tryDocking(); + } + + } + + @Override + public void componentHidden(ComponentEvent e) { + // System.out.println("ed hidden"); + } + }); + + } + + + /** + * Implements the docking feature of the tool - The frame sticks to the + * editor and once docked, moves along with it as the editor is resized, + * moved, or closed. + * + * This class has been borrowed from Tab Manager tool by Thomas Diewald. It + * has been slightly modified and used here. + * + * @author Thomas Diewald , http://thomasdiewald.com + */ + private class DockTool2Base { + + private int docking_border = 0; + private int dock_on_editor_y_offset_ = 0; + private int dock_on_editor_x_offset_ = 0; + + // /////////////////////////////// + // ____2____ + // | | + // | | + // 0 | editor | 1 + // | | + // |_________| + // 3 + // /////////////////////////////// + + // public void reset() { + // dock_on_editor_y_offset_ = 0; + // dock_on_editor_x_offset_ = 0; + // docking_border = 0; + // } + + public boolean isDocked() { + return (docking_border >= 0); + } + + private final int MAX_GAP_ = 20; + + // + public void tryDocking() { + if (thisEditor == null) + return; + Editor editor = thisEditor; + Frame frame = thisErrorWindow; + + int ex = editor.getX(); + int ey = editor.getY(); + int ew = editor.getWidth(); + int eh = editor.getHeight(); + + int fx = frame.getX(); + int fy = frame.getY(); + int fw = frame.getWidth(); + int fh = frame.getHeight(); + + if (((fy > ey) && (fy < ey + eh)) + || ((fy + fh > ey) && (fy + fh < ey + eh))) { + int dis_border_left = Math.abs(ex - (fx + fw)); + int dis_border_right = Math.abs((ex + ew) - (fx)); + + if (dis_border_left < MAX_GAP_ || dis_border_right < MAX_GAP_) { + docking_border = (dis_border_left < dis_border_right) ? 0 + : 1; + dock_on_editor_y_offset_ = fy - ey; + dock(); + return; + } + } + + if (((fx > ex) && (fx < ex + ew)) + || ((fx + fw > ey) && (fx + fw < ex + ew))) { + int dis_border_top = Math.abs(ey - (fy + fh)); + int dis_border_bot = Math.abs((ey + eh) - (fy)); + + if (dis_border_top < MAX_GAP_ || dis_border_bot < MAX_GAP_) { + docking_border = (dis_border_top < dis_border_bot) ? 2 : 3; + dock_on_editor_x_offset_ = fx - ex; + dock(); + return; + } + } + docking_border = -1; + } + + public void dock() { + if (thisEditor == null) + return; + Editor editor = thisEditor; + Frame frame = thisErrorWindow; + + int ex = editor.getX(); + int ey = editor.getY(); + int ew = editor.getWidth(); + int eh = editor.getHeight(); + + // int fx = frame.getX(); + // int fy = frame.getY(); + int fw = frame.getWidth(); + int fh = frame.getHeight(); + + int x = 0, y = 0; + if (docking_border == -1) { + return; + } + + if (docking_border == 0) { + x = ex - fw; + y = ey + dock_on_editor_y_offset_; + } + if (docking_border == 1) { + x = ex + ew; + y = ey + dock_on_editor_y_offset_; + } + + if (docking_border == 2) { + x = ex + dock_on_editor_x_offset_; + y = ey - fh; + } + if (docking_border == 3) { + x = ex + dock_on_editor_x_offset_; + y = ey + eh; + } + frame.setLocation(x, y); + } + + } +} diff --git a/app/src/processing/mode/java2/ImportStatement.java b/app/src/processing/mode/java2/ImportStatement.java new file mode 100755 index 000000000..87f3c86bc --- /dev/null +++ b/app/src/processing/mode/java2/ImportStatement.java @@ -0,0 +1,51 @@ +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.java2; + +/** + * Wrapper for import statements + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class ImportStatement { + /** + * Ex: processing.opengl.*, java.util.* + */ + String importName; + /** + * Which tab does it belong to? + */ + int tab; + + /** + * Line number(pde code) of the import + */ + int lineNumber; + + public ImportStatement(String importName, int tab, int lineNumber) { + this.importName = importName; + this.tab = tab; + this.lineNumber = lineNumber; + } + } \ No newline at end of file diff --git a/app/src/processing/mode/java2/Problem.java b/app/src/processing/mode/java2/Problem.java new file mode 100755 index 000000000..22808d1e6 --- /dev/null +++ b/app/src/processing/mode/java2/Problem.java @@ -0,0 +1,160 @@ +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.java2; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.core.compiler.IProblem; + +/** + * Wrapper class for IProblem. + * + * Stores the tabIndex and line number according to its tab, including the + * original IProblem object + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class Problem { + /** + * The IProblem which is being wrapped + */ + private IProblem iProblem; + /** + * The tab number to which the error belongs to + */ + public int tabIndex; + /** + * Line number(pde code) of the error + */ + public int lineNumber; + + /** + * Error Message. Processed form of IProblem.getMessage() + */ + public String message; + + public int type; + + public static final int ERROR = 1, WARNING = 2; + + public Problem(IProblem iProblem, int tabIndex, int lineNumber) { + this.iProblem = iProblem; + if(iProblem.isError()) { + type = ERROR; + } + else if(iProblem.isWarning()) { + type = WARNING; + } + this.tabIndex = tabIndex; + this.lineNumber = lineNumber; + this.message = process(iProblem); + } + + public String toString() { + return new String("TAB " + tabIndex + ",LN " + lineNumber + ",PROB: " + + message); + } + + public boolean isError(){ + return type == ERROR; + } + + public boolean isWarning(){ + return type == WARNING; + } + + public String getMessage(){ + return message; + } + + public IProblem getIProblem(){ + return iProblem; + } + + public void setType(int ProblemType){ + if(ProblemType == ERROR) + type = ERROR; + else if(ProblemType == WARNING) + type = WARNING; + else throw new IllegalArgumentException("Illegal Problem type passed to Problem.setType(int)"); + } + + static Pattern pattern; + static Matcher matcher; + + static final String tokenRegExp = "\\b token\\b"; + + public static String process(IProblem problem) { + return process(problem.getMessage()); + } + + /** + * Processes error messages and attempts to make them a bit more english like. + * Currently performs: + *
  • Remove all instances of token. "Syntax error on token 'blah', delete this token" + * becomes "Syntax error on 'blah', delete this" + * @param message - The message to be processed + * @return String - The processed message + */ + public static String process(String message) { + // Remove all instances of token + // "Syntax error on token 'blah', delete this token" + + pattern = Pattern.compile(tokenRegExp); + matcher = pattern.matcher(message); + message = matcher.replaceAll(""); + + // Split camel case words into separate words. + // "VaraibleDeclaration" becomes "Variable Declaration" + // But sadly "PApplet" become "P Applet" and so on. + + // StringTokenizer st = new StringTokenizer(message); + // String newMessage = ""; + // while (st.hasMoreTokens()) { + // String word = st.nextToken(); + // newMessage += splitCamelCaseWord(word) + " "; + // } + // message = new String(newMessage); + + return message; + } + + public static String splitCamelCaseWord(String word) { + String newWord = ""; + for (int i = 1; i < word.length(); i++) { + if (Character.isUpperCase(word.charAt(i))) { + // System.out.println(word.substring(0, i) + " " + // + word.substring(i)); + newWord += word.substring(0, i) + " "; + word = word.substring(i); + i = 1; + } + } + newWord += word; + // System.out.println(newWord); + return newWord.trim(); + } + +} diff --git a/app/src/processing/mode/java2/TextArea.java b/app/src/processing/mode/java2/TextArea.java index 56081c1df..1400623e1 100755 --- a/app/src/processing/mode/java2/TextArea.java +++ b/app/src/processing/mode/java2/TextArea.java @@ -26,6 +26,7 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.HashMap; import java.util.Map; + import processing.app.syntax.JEditTextArea; import processing.app.syntax.TextAreaDefaults; @@ -48,7 +49,8 @@ public class TextArea extends JEditTextArea { protected String currentLineMarker = "->"; // the text marker for highlighting the current line in the gutter protected Map gutterText = new HashMap(); // maps line index to gutter text protected Map gutterTextColors = new HashMap(); // maps line index to gutter text color - + protected TextAreaPainter customPainter; + public TextArea(TextAreaDefaults defaults, DebugEditor editor) { super(defaults); this.editor = editor; @@ -62,8 +64,9 @@ public class TextArea extends JEditTextArea { remove(painter); // set new painter - painter = new TextAreaPainter(this, defaults); - + customPainter = new TextAreaPainter(this, defaults); + painter = customPainter; + // set listeners for (ComponentListener cl : componentListeners) { painter.addComponentListener(cl); @@ -88,6 +91,11 @@ public class TextArea extends JEditTextArea { breakpointMarker = theme.loadThemeString("breakpoint.marker", breakpointMarker); currentLineMarker = theme.loadThemeString("currentline.marker", currentLineMarker); } + + public void setECSandThemeforTextArea(ErrorCheckerService ecs, DebugMode mode) + { + customPainter.setECSandTheme(ecs, mode); + } /** * Retrieve the total width of the gutter area. @@ -130,7 +138,7 @@ public class TextArea extends JEditTextArea { * * @param lineIdx the line index (0-based) * @param text the text - * @param textColor the text colorÏ + * @param textColor the text color */ public void setGutterText(int lineIdx, String text, Color textColor) { gutterTextColors.put(lineIdx, textColor); diff --git a/app/src/processing/mode/java2/TextAreaPainter.java b/app/src/processing/mode/java2/TextAreaPainter.java index 99fd0ef25..8db27a60a 100755 --- a/app/src/processing/mode/java2/TextAreaPainter.java +++ b/app/src/processing/mode/java2/TextAreaPainter.java @@ -19,8 +19,11 @@ package processing.mode.java2; import java.awt.Color; import java.awt.Graphics; + +import javax.swing.text.BadLocationException; import javax.swing.text.Segment; import javax.swing.text.Utilities; + import processing.app.syntax.TextAreaDefaults; import processing.app.syntax.TokenMarker; @@ -33,11 +36,43 @@ import processing.app.syntax.TokenMarker; public class TextAreaPainter extends processing.app.syntax.TextAreaPainter { protected TextArea ta; // we need the subclassed textarea + protected ErrorCheckerService errorCheckerService; + + /** + * Error line underline color + */ + public Color errorColor = new Color(0xED2630); + /** + * Warning line underline color + */ + + public Color warningColor = new Color(0xFFC30E); + + /** + * Color of Error Marker + */ + public Color errorMarkerColor = new Color(0xED2630); + + /** + * Color of Warning Marker + */ + public Color warningMarkerColor = new Color(0xFFC30E); + public TextAreaPainter(TextArea textArea, TextAreaDefaults defaults) { super(textArea, defaults); ta = textArea; } + + private void loadTheme(DebugMode mode){ + errorColor = mode.getThemeColor("editor.errorcolor", errorColor); + warningColor = mode.getThemeColor("editor.warningcolor", + warningColor); + errorMarkerColor = mode.getThemeColor("editor.errormarkercolor", + errorMarkerColor); + warningMarkerColor = mode.getThemeColor( + "editor.warningmarkercolor", warningMarkerColor); + } /** * Paint a line. Paints the gutter (with background color and text) then the @@ -61,7 +96,9 @@ public class TextAreaPainter extends processing.app.syntax.TextAreaPainter { // paint gutter symbol paintGutterText(gfx, line, x); - + + paintErrorLine(gfx, line, x); + super.paintLine(gfx, tokenMarker, line, x + ta.getGutterWidth()); } @@ -145,4 +182,132 @@ public class TextAreaPainter extends processing.app.syntax.TextAreaPainter { gfx.setColor(col); gfx.fillRect(0, y, getWidth(), height); } + + /** + * Paints the underline for an error/warning line + * + * @param gfx + * the graphics context + * @param tokenMarker + * @param line + * 0-based line number: NOTE + * @param x + */ + private void paintErrorLine(Graphics gfx, int line, int x) { + + if (errorCheckerService == null) { + return; + } + + if (errorCheckerService.problemsList== null) { + return; + } + + boolean notFound = true; + boolean isWarning = false; + + // Check if current line contains an error. If it does, find if it's an + // error or warning + for (ErrorMarker emarker : errorCheckerService.getEditor().errorBar.errorPoints) { + if (emarker.problem.lineNumber == line + 1) { + notFound = false; + if (emarker.type == ErrorMarker.Warning) { + isWarning = true; + } + break; + } + } + + if (notFound) { + return; + } + + // Determine co-ordinates + // System.out.println("Hoff " + ta.getHorizontalOffset() + ", " + + // horizontalAdjustment); + int y = ta.lineToY(line); + y += fm.getLeading() + fm.getMaxDescent(); + int height = fm.getHeight(); + int start = ta.getLineStartOffset(line); + + try { + String linetext = null; + + try { + linetext = ta.getDocument().getText(start, + ta.getLineStopOffset(line) - start - 1); + } catch (BadLocationException bl) { + // Error in the import statements or end of code. + // System.out.print("BL caught. " + ta.getLineCount() + " ," + // + line + " ,"); + // System.out.println((ta.getLineStopOffset(line) - start - 1)); + return; + } + + // Take care of offsets + int aw = fm.stringWidth(trimRight(linetext)) + + ta.getHorizontalOffset(); // apparent width. Whitespaces + // to the left of line + text + // width + int rw = fm.stringWidth(linetext.trim()); // real width + int x1 = 0 + (aw - rw), y1 = y + fm.getHeight() - 2, x2 = x1 + rw; + // Adding offsets for the gutter + x1 += 20; + x2 += 20; + + // gfx.fillRect(x1, y, rw, height); + + // Let the painting begin! + gfx.setColor(errorMarkerColor); + if (isWarning) { + gfx.setColor(warningMarkerColor); + } + gfx.fillRect(1, y + 2, 3, height - 2); + + gfx.setColor(errorColor); + if (isWarning) { + gfx.setColor(warningColor); + } + int xx = x1; + + // Draw the jagged lines + while (xx < x2) { + gfx.drawLine(xx, y1, xx + 2, y1 + 1); + xx += 2; + gfx.drawLine(xx, y1 + 1, xx + 2, y1); + xx += 2; + } + } catch (Exception e) { + System.out + .println("Looks like I messed up! XQTextAreaPainter.paintLine() : " + + e); + //e.printStackTrace(); + } + + // Won't highlight the line. Select the text instead. + // gfx.setColor(Color.RED); + // gfx.fillRect(2, y, 3, height); + } + + /** + * Trims out trailing whitespaces (to the right) + * + * @param string + * @return - String + */ + private String trimRight(String string) { + String newString = ""; + for (int i = 0; i < string.length(); i++) { + if (string.charAt(i) != ' ') { + newString = string.substring(0, i) + string.trim(); + break; + } + } + return newString; + } + + public void setECSandTheme(ErrorCheckerService ecs, DebugMode mode){ + this.errorCheckerService = ecs; + loadTheme(mode); + } } diff --git a/app/src/processing/mode/java2/XQConsoleToggle.java b/app/src/processing/mode/java2/XQConsoleToggle.java new file mode 100755 index 000000000..bfafdb22f --- /dev/null +++ b/app/src/processing/mode/java2/XQConsoleToggle.java @@ -0,0 +1,131 @@ +package processing.mode.java2; + +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.JPanel; + +/** + * Toggle Button displayed in the editor line status panel for toggling bewtween + * console and problems list. Glorified JPanel. + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ + +public class XQConsoleToggle extends JPanel implements MouseListener { + public static final String[] text = { "Console", "Errors" }; + + private boolean toggleText = true; + private boolean toggleBG = true; + + /** + * Height of the component + */ + protected int height; + protected DebugEditor editor; + protected String buttonName; + + public XQConsoleToggle(DebugEditor editor, String buttonName, int height) { + this.editor = editor; + this.height = height; + this.buttonName = buttonName; + } + + public Dimension getPreferredSize() { + return new Dimension(70, height); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + public void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + // On mouse hover, text and background color are changed. + if (toggleBG) { + g.setColor(new Color(0xff9DA7B0)); + g.fillRect(0, 0, this.getWidth(), this.getHeight()); + g.setColor(new Color(0xff29333D)); + g.fillRect(0, 0, 4, this.getHeight()); + g.setColor(Color.BLACK); + } else { + g.setColor(Color.DARK_GRAY); + g.fillRect(0, 0, this.getWidth(), this.getHeight()); + g.setColor(new Color(0xff29333D)); + g.fillRect(0, 0, 4, this.getHeight()); + g.setColor(Color.WHITE); + } + + g.drawString(buttonName, getWidth() / 2 + 2 // + 2 is a offset + - getFontMetrics(getFont()).stringWidth(buttonName) / 2, + this.getHeight() - 6); + } + + @Override + public void mouseClicked(MouseEvent arg0) { + + this.repaint(); + try { + editor.toggleView(buttonName); + } catch (Exception e) { + System.out.println(e); + // e.printStackTrace(); + } + toggleText = !toggleText; + } + + @Override + public void mouseEntered(MouseEvent arg0) { + toggleBG = !toggleBG; + this.repaint(); + } + + @Override + public void mouseExited(MouseEvent arg0) { + toggleBG = !toggleBG; + this.repaint(); + } + + @Override + public void mousePressed(MouseEvent arg0) { + } + + @Override + public void mouseReleased(MouseEvent arg0) { + } +} \ No newline at end of file diff --git a/app/src/processing/mode/java2/XQErrorTable.java b/app/src/processing/mode/java2/XQErrorTable.java new file mode 100755 index 000000000..4218c2820 --- /dev/null +++ b/app/src/processing/mode/java2/XQErrorTable.java @@ -0,0 +1,161 @@ +package processing.mode.java2; + +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import javax.swing.JTable; +import javax.swing.SwingWorker; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableModel; + +/** + * Custom JTable implementation for XQMode. Minor tweaks and addtions. + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class XQErrorTable extends JTable { + + /** + * Column Names of JTable + */ + public static final String[] columnNames = { "Problem", "Tab", "Line" }; + + /** + * Column Widths of JTable. + */ + public int[] columnWidths = { 600, 100, 50 }; // Default Values + + /** + * Is the column being resized? + */ + private boolean columnResizing = false; + + protected ErrorCheckerService errorCheckerService; + + @Override + public boolean isCellEditable(int rowIndex, int colIndex) { + return false; // Disallow the editing of any cell + } + + public XQErrorTable(final ErrorCheckerService errorCheckerService) { + this.errorCheckerService = errorCheckerService; + for (int i = 0; i < this.getColumnModel().getColumnCount(); i++) { + this.getColumnModel().getColumn(i) + .setPreferredWidth(columnWidths[i]); + } + + this.getTableHeader().setReorderingAllowed(false); + + this.addMouseListener(new MouseAdapter() { + @Override + synchronized public void mouseReleased(MouseEvent e) { + try { + errorCheckerService.scrollToErrorLine(((XQErrorTable) e + .getSource()).getSelectedRow()); + // System.out.print("Row clicked: " + // + ((XQErrorTable) e.getSource()).getSelectedRow()); + } catch (Exception e1) { + System.out.println("Exception XQErrorTable mouseReleased " + + e); + } + } + }); + + // Handles the resizing of columns. When mouse press is detected on + // table header, Stop updating the table, store new values of column + // widths,and resume updating. Updating is disabled as long as + // columnResizing is true + this.getTableHeader().addMouseListener(new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + columnResizing = true; + } + + @Override + public void mouseReleased(MouseEvent e) { + columnResizing = false; + for (int i = 0; i < ((JTableHeader) e.getSource()) + .getColumnModel().getColumnCount(); i++) { + columnWidths[i] = ((JTableHeader) e.getSource()) + .getColumnModel().getColumn(i).getWidth(); + // System.out.println("nw " + columnWidths[i]); + } + } + }); + } + + + /** + * Updates table contents with new data + * @param tableModel - TableModel + * @return boolean - If table data was updated + */ + @SuppressWarnings("rawtypes") + synchronized public boolean updateTable(final TableModel tableModel) { + + // If problems list is not visible, no need to update + if (!this.isVisible()) + return false; + + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + return null; + } + + protected void done() { + + try { + setModel(tableModel); + + // Set column widths to user defined widths + for (int i = 0; i < getColumnModel().getColumnCount(); i++) { + getColumnModel().getColumn(i).setPreferredWidth( + columnWidths[i]); + } + getTableHeader().setReorderingAllowed(false); + validate(); + repaint(); + } catch (Exception e) { + System.out.println("Exception at XQErrorTable.updateTable " + e); + // e.printStackTrace(); + } + } + }; + + try { + if (!columnResizing) + worker.execute(); + } catch (Exception e) { + System.out.println("ErrorTable updateTable Worker's slacking." + + e.getMessage()); + // e.printStackTrace(); + } + return true; + } + +} diff --git a/app/src/processing/mode/java2/XQPreprocessor.java b/app/src/processing/mode/java2/XQPreprocessor.java new file mode 100755 index 000000000..4a92f74b2 --- /dev/null +++ b/app/src/processing/mode/java2/XQPreprocessor.java @@ -0,0 +1,245 @@ +/* + Part of the XQMode project - https://github.com/Manindra29/XQMode + + Under Google Summer of Code 2012 - + http://www.google-melange.com/gsoc/homepage/google/gsoc2012 + + Copyright (C) 2012 Manindra Moharana + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.java2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.TextEdit; + +import processing.app.Preferences; +import processing.core.PApplet; + +/** + * My implementation of P5 preprocessor. Uses Eclipse JDT features instead of + * ANTLR. Performance gains mostly and full control over debug output. But needs + * lots and lots of testing. There will always an option to switch back to PDE + * preproc. + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ +public class XQPreprocessor { + + private ASTRewrite rewrite = null; + public int mainClassOffset = 0; + ArrayList imports; + ArrayList extraImports; + + /** + * The main method that performs preprocessing. Converts code into compilable java. + * @param source - String + * @param programImports - List of import statements + * @return String - Compile ready java code + */ + public String doYourThing(String source, + ArrayList programImports) { + this.extraImports = programImports; + source = prepareImports() + source; + Document doc = new Document(source); + + ASTParser parser = ASTParser.newParser(AST.JLS4); + parser.setSource(doc.get().toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + @SuppressWarnings("unchecked") + Map options = JavaCore.getOptions(); + + // Ben has decided to move on to 1.6. Yay! + JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_6); + parser.setCompilerOptions(options); + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + cu.recordModifications(); + rewrite = ASTRewrite.create(cu.getAST()); + cu.accept(new XQASTVisitor()); + + TextEdit edits = cu.rewrite(doc, null); + try { + edits.apply(doc); + } catch (MalformedTreeException e) { + e.printStackTrace(); + } catch (BadLocationException e) { + e.printStackTrace(); + } + // System.out.println("------------XQPreProc-----------------"); + // System.out.println(doc.get()); + // System.out.println("------------XQPreProc End-----------------"); + + // Calculate main class offset + int position = doc.get().indexOf("{") + 1; + int lines = 0; + for (int i = 0; i < position; i++) { + if (doc.get().charAt(i) == '\n') + lines++; + } + lines += 2; + // System.out.println("Lines: " + lines); + mainClassOffset = lines; + + return doc.get(); + } + + /** + * Returns all import statements as lines of code + * + * @return String - All import statements combined + */ + public String prepareImports() { + imports = new ArrayList(); + for (int i = 0; i < extraImports.size(); i++) { + imports.add(new String(extraImports.get(i).importName)); + } + imports.add(new String("// Default Imports")); + for (int i = 0; i < getCoreImports().length; i++) { + imports.add(new String("import " + getCoreImports()[i] + ";")); + } + for (int i = 0; i < getDefaultImports().length; i++) { + imports.add(new String("import " + getDefaultImports()[i] + ";")); + } + String totalImports = ""; + for (int i = 0; i < imports.size(); i++) { + totalImports += imports.get(i) + "\n"; + } + totalImports += "\n"; + return totalImports; + } + + public String[] getCoreImports() { + return new String[] { "processing.core.*", "processing.data.*", + "processing.opengl.*" }; + } + + public String[] getDefaultImports() { + // These may change in-between (if the prefs panel adds this option) + String prefsLine = Preferences.get("preproc.imports.list"); + return PApplet.splitTokens(prefsLine, ", "); + } + + /** + * Visitor implementation that does all the substitution dirty work.
    + *
  • Any function not specified as being protected or private will be made + * 'public'. This means that void setup() becomes + * public void setup(). + * + *
  • Converts doubles into floats, i.e. 12.3 becomes 12.3f so that people + * don't have to add f after their numbers all the time since it's confusing + * for beginners. Also, most functions of p5 core deal with floats only. + * + * @author Manindra Moharana + * + */ + private class XQASTVisitor extends ASTVisitor { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public boolean visit(MethodDeclaration node) { + if (node.getReturnType2() != null) { + // if return type is color, make it int + // if (node.getReturnType2().toString().equals("color")) { + // System.err.println("color type detected!"); + // node.setReturnType2(rewrite.getAST().newPrimitiveType( + // PrimitiveType.INT)); + // } + + // The return type is not void, no need to make it public + // if (!node.getReturnType2().toString().equals("void")) + // return true; + } + + // Simple method, make it public + if (node.modifiers().size() == 0 && !node.isConstructor()) { + // rewrite.set(node, node.getModifiersProperty(), + // Modifier.PUBLIC, + // null); + // rewrite.getListRewrite(node, + // node.getModifiersProperty()).insertLast(Modifier., null) + List newMod = rewrite.getAST().newModifiers(Modifier.PUBLIC); + node.modifiers().add(newMod.get(0)); + } + + return true; + } + + public boolean visit(NumberLiteral node) { + if (!node.getToken().endsWith("f") + && !node.getToken().endsWith("d")) { + for (int i = 0; i < node.getToken().length(); i++) { + if (node.getToken().charAt(i) == '.') { + + String s = node.getToken() + "f"; + node.setToken(s); + break; + } + } + } + return true; + } + + // public boolean visit(FieldDeclaration node) { + // if (node.getType().toString().equals("color")){ + // System.err.println("color type detected!"); + // node.setType(rewrite.getAST().newPrimitiveType( + // PrimitiveType.INT)); + // } + // return true; + // } + // + // public boolean visit(VariableDeclarationStatement node) { + // if (node.getType().toString().equals("color")){ + // System.err.println("color type detected!"); + // node.setType(rewrite.getAST().newPrimitiveType( + // PrimitiveType.INT)); + // } + // return true; + // } + + /** + * This is added just for debugging purposes - to make sure that all + * instances of color type have been substituded as in by the regex + * search in ErrorCheckerService.preprocessCode(). + */ + public boolean visit(SimpleType node) { + if (node.toString().equals("color")) { + System.err + .println("color type detected! \nThis shouldn't be happening! Please report this as an issue on www.github.com/Manindra29/XQMode"); + } + return true; + + } + + } + +} diff --git a/java2/mode/readme.txt b/java2/mode/readme.txt new file mode 100644 index 000000000..3ec74a7e8 --- /dev/null +++ b/java2/mode/readme.txt @@ -0,0 +1,3 @@ +Packages from Eclipse 4.2.1: +http://download.eclipse.org/eclipse/downloads/ + diff --git a/todo.txt b/todo.txt index 167d716ae..53dba9fe3 100644 --- a/todo.txt +++ b/todo.txt @@ -106,8 +106,6 @@ _ "To use this library, you must use the 32-bit version of Processing." _ temporary files (for sketches and logs) are not deleted _ http://code.google.com/p/processing/issues/detail?id=562 -http://download.eclipse.org/eclipse/downloads/ - _ renaming RGB (.pde) to Rgb.java says "a file named RGB.pde already exists" _ change cmd line for OS X to use symlink?