diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 91472a09e9..79d19e4908 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1526,6 +1526,7 @@ public boolean handleClose(Editor editor, boolean modeSwitch) { editor.setVisible(false); editor.dispose(); defaultFileMenu.insert(Recent.getMenu(), 2); + Toolkit.dark(defaultFileMenu.getPopupMenu()); activeEditor = null; editors.remove(editor); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 7d3caf8988..135de4adc1 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -39,8 +39,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.*; @@ -76,7 +75,7 @@ public class Sketch { private File codeFolder; private SketchCode current; - private int currentIndex; + private int currentTabIndex; /** * Number of sketchCode objects (tabs) in the current sketch. Note that this @@ -87,6 +86,7 @@ public class Sketch { */ private int codeCount; private SketchCode[] code; + private List openTabs; /** Moved out of Editor and into here for cleaner access. */ private boolean untitled; @@ -155,11 +155,16 @@ protected void load() { codeCount = filenames.size(); code = new SketchCode[codeCount]; + openTabs = new ArrayList<>(); for (int i = 0; i < codeCount; i++) { String filename = filenames.get(i); String extension = extensions.get(i); - code[i] = new SketchCode(new File(folder, filename), extension); + SketchCode sketchCode = new SketchCode(new File(folder, filename), extension); + code[i] = sketchCode; + if (codeCount < 10) { // if there are many tabs, start out with only the main + openTabs.add(sketchCode); + } } // move the main class to the first tab @@ -173,6 +178,15 @@ protected void load() { break; } } + if (!openTabs.contains(code[0])) openTabs.add(0, code[0]); + else for (int i = 1; i < getTabCount(); i++) { + if (getTab(i).getFile().equals(primaryFile)) { + SketchCode temp = getTab(0); + openTabs.set(0, getTab(i)); + openTabs.set(i, temp); + break; + } + } // sort the entries at the top sortCode(); @@ -240,7 +254,7 @@ public void loadNewTab(String filename, String ext, boolean newAddition) { } else { replaceCode(new SketchCode(new File(folder, filename), ext)); } - sortCode(); + // sortCode(); no } @@ -261,6 +275,7 @@ protected void insertCode(SketchCode newCode) { // add file to the code/codeCount list, resort the list //if (codeCount == code.length) { code = (SketchCode[]) PApplet.append(code, newCode); + openTabs.add(newCode); codeCount++; //} //code[codeCount++] = newCode; @@ -270,23 +285,24 @@ protected void insertCode(SketchCode newCode) { protected void sortCode() { // cheap-ass sort of the rest of the files // it's a dumb, slow sort, but there shouldn't be more than ~5 files - for (int i = 1; i < codeCount; i++) { + int tabCount = getTabCount(); + for (int i = 1; i < tabCount; i++) { int who = i; - for (int j = i + 1; j < codeCount; j++) { - if (code[j].getFileName().compareTo(code[who].getFileName()) < 0) { + for (int j = i + 1; j < tabCount; j++) { + if (getTab(j).getFileName().compareTo(getTab(who).getFileName()) < 0) { who = j; // this guy is earlier in the alphabet } } if (who != i) { // swap with someone if changes made - SketchCode temp = code[who]; - code[who] = code[i]; - code[i] = temp; + SketchCode temp = getTab(who); + openTabs.set(who, getTab(i)); + openTabs.set(i, temp); // We also need to update the current tab - if (currentIndex == i) { - currentIndex = who; - } else if (currentIndex == who) { - currentIndex = i; + if (currentTabIndex == i) { + currentTabIndex = who; + } else if (currentTabIndex == who) { + currentTabIndex = i; } } } @@ -323,7 +339,7 @@ public void handleRenameCode() { // make sure the user didn't hide the sketch folder ensureExistence(); - if (currentIndex == 0 && isUntitled()) { + if (currentTabIndex == 0 && isUntitled()) { Messages.showMessage(Language.text("rename.messages.is_untitled"), Language.text("rename.messages.is_untitled.description")); return; @@ -346,7 +362,7 @@ public void handleRenameCode() { // ask for new name of file (internal to window) // TODO maybe just popup a text area? renamingCode = true; - String prompt = (currentIndex == 0) ? + String prompt = (currentTabIndex == 0) ? Language.text("editor.sketch.rename.description") : Language.text("editor.tab.rename.description"); String oldName = (current.isExtension(mode.getDefaultExtension())) ? @@ -526,7 +542,7 @@ protected void nameCode(String newName) { File newFile = new File(folder, newName); if (renamingCode) { - if (currentIndex == 0) { + if (currentTabIndex == 0) { // get the new folder name/location String folderName = newName.substring(0, newName.indexOf('.')); File newFolder = new File(folder.getParentFile(), folderName); @@ -617,8 +633,8 @@ protected void nameCode(String newName) { insertCode(newCode); } - // sort the entries - sortCode(); + // don't sort the entries + // sortCode(); // set the new guy as current setCurrentCode(newName); @@ -626,81 +642,107 @@ protected void nameCode(String newName) { // update the tabs editor.rebuildHeader(); } - - + + /** * Remove a piece of code from the sketch and from the disk. */ public void handleDeleteCode() { // make sure the user didn't hide the sketch folder ensureExistence(); - + // if read-only, give an error if (isReadOnly()) { // if the files are read-only, need to first do a "save as". Messages.showMessage(Language.text("delete.messages.is_read_only"), - Language.text("delete.messages.is_read_only.description")); + Language.text("delete.messages.is_read_only.description")); return; } - + // don't allow if untitled - if (currentIndex == 0 && isUntitled()) { + if (currentTabIndex == 0 && isUntitled()) { Messages.showMessage(Language.text("delete.messages.cannot_delete"), - Language.text("delete.messages.cannot_delete.description")); + Language.text("delete.messages.cannot_delete.description")); return; } - + // confirm deletion with user, yes/no Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") }; - String prompt = (currentIndex == 0) ? + String prompt = (currentTabIndex == 0) ? Language.text("warn.delete.sketch") : Language.interpolate("warn.delete.file", current.getPrettyName()); int result = JOptionPane.showOptionDialog(editor, - prompt, - Language.text("warn.delete"), - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); + prompt, + Language.text("warn.delete"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); if (result == JOptionPane.YES_OPTION) { - if (currentIndex == 0) { + if (currentTabIndex == 0) { // need to unset all the modified flags, otherwise tries // to do a save on the handleNew() - + // delete the entire sketch Util.removeDir(folder); - + // get the changes into the sketchbook menu //sketchbook.rebuildMenus(); - + // make a new sketch and rebuild the sketch menu //editor.handleNewUnchecked(); //editor.handleClose2(); editor.getBase().rebuildSketchbookMenus(); editor.getBase().handleClose(editor, false); - + } else { // delete the file if (!current.deleteFile()) { Messages.showMessage(Language.text("delete.messages.cannot_delete.file"), - Language.text("delete.messages.cannot_delete.file.description")+" \"" + - current.getFileName() + "\"."); + Language.text("delete.messages.cannot_delete.file.description")+" \"" + + current.getFileName() + "\"."); return; } - + // remove code from the list removeCode(current); - + // update the tabs editor.rebuildHeader(); - + // just set current tab to the main tab setCurrentCode(0); - + } } } + + + /** + * Close a tab of a sketch + */ + public void handleCloseTab() { + // don't allow if untitled + if (isUntitled()) { + Messages.showMessage(Language.text("close.messages.cannot_delete"), + Language.text("close.messages.cannot_delete.description")); + return; + } + + closeTab(current); + } + + public void closeTab(SketchCode tab) { + int index = currentTabIndex; + openTabs.remove(tab); + + // update the tabs + editor.rebuildHeader(); + + // set to previous tab. index shouldn't be 0 as that'd be closing the main tab + setCurrentTab(index-1); + } /** @@ -710,6 +752,7 @@ public void handleDeleteCode() { public void removeCode(SketchCode which) { // remove it from the internal list of files // resort internal list of files + openTabs.remove(which); for (int i = 0; i < codeCount; i++) { if (code[i] == which) { for (int j = i; j < codeCount-1; j++) { @@ -728,9 +771,9 @@ public void removeCode(SketchCode which) { * Move to the previous tab. */ public void handlePrevCode() { - int prev = currentIndex - 1; - if (prev < 0) prev = codeCount-1; - setCurrentCode(prev); + int prev = currentTabIndex - 1; + if (prev < 0) prev = getTabCount()-1; + setCurrentTab(prev); } @@ -738,7 +781,7 @@ public void handlePrevCode() { * Move to the next tab. */ public void handleNextCode() { - setCurrentCode((currentIndex + 1) % codeCount); + setCurrentTab((currentTabIndex + 1) % getTabCount()); } @@ -1373,7 +1416,7 @@ public boolean addFile(File sourceFile) { } else { insertCode(newCode); - sortCode(); + // sortCode(); } setCurrentCode(filename); editor.repaintHeader(); @@ -1397,22 +1440,37 @@ public boolean addFile(File sourceFile) { * Change what file is currently being edited. Changes the current tab index. *
    *
  1. store the String for the text of the current file. + *
  2. open the tab if it's not open already. *
  3. retrieve the String for the text of the new file. *
  4. change the text that's visible in the text area *
*/ public void setCurrentCode(int which) { + if (which < 0 || which >= getCodeCount()) return; + + setCurrentCode(code[which]); + } + public void setCurrentCode(SketchCode tab) { + if (!openTabs.contains(tab)) { + openTabs.add(tab); + + editor.rebuildHeader(); + } + + setCurrentTab(openTabs.indexOf(tab)); + } + public void setCurrentTab(int which) { // // for the tab sizing // if (current != null) { // current.visited = System.currentTimeMillis(); // System.out.println(current.visited); // } // if current is null, then this is the first setCurrent(0) - if (which < 0 || which >= codeCount || - ((currentIndex == which) && (current == code[currentIndex]))) { + if (which < 0 || which >= getTabCount() || + ((currentTabIndex == which) && (current == openTabs.get(currentTabIndex)))) { return; } - + // get the text currently being edited if (current != null) { current.setState(editor.getText(), @@ -1420,9 +1478,9 @@ public void setCurrentCode(int which) { editor.getSelectionStop(), editor.getScrollPosition()); } - - current = code[which]; - currentIndex = which; + + current = getTab(which); + currentTabIndex = which; current.visited = System.currentTimeMillis(); editor.setCode(current); @@ -1435,9 +1493,9 @@ public void setCurrentCode(int which) { * @param findName the file name (not pretty name) to be shown */ public void setCurrentCode(String findName) { - for (int i = 0; i < codeCount; i++) { - if (findName.equals(code[i].getFileName()) || - findName.equals(code[i].getPrettyName())) { + for (int i = 0; i < getCodeCount(); i++) { + if (findName.equals(getCode(i).getFileName()) || + findName.equals(getCode(i).getPrettyName())) { setCurrentCode(i); return; } @@ -1668,16 +1726,25 @@ public File prepareCodeFolder() { public SketchCode[] getCode() { return code; } - - + public List getTabs() { + return openTabs; + } + + public int getCodeCount() { return codeCount; } + public int getTabCount() { + return openTabs.size(); + } public SketchCode getCode(int index) { return code[index]; } + public SketchCode getTab(int index) { + return openTabs.get(index); + } public int getCodeIndex(SketchCode who) { @@ -1696,7 +1763,7 @@ public SketchCode getCurrentCode() { public int getCurrentCodeIndex() { - return currentIndex; + return getCodeIndex(getTab(currentTabIndex)); } diff --git a/app/src/processing/app/syntax/JEditTextArea.java b/app/src/processing/app/syntax/JEditTextArea.java index b63084c63a..ee6781e5d8 100644 --- a/app/src/processing/app/syntax/JEditTextArea.java +++ b/app/src/processing/app/syntax/JEditTextArea.java @@ -24,10 +24,12 @@ import javax.swing.text.*; import javax.swing.undo.*; import javax.swing.*; +import javax.swing.plaf.basic.*; import java.awt.im.InputMethodRequests; import processing.app.syntax.im.InputMethodSupport; +import processing.app.ui.DarkScrollBarUI; import processing.core.PApplet; /** @@ -64,7 +66,7 @@ * * @author Slava Pestov */ -public class JEditTextArea extends JComponent +public class JEditTextArea extends JPanel { /** * Adding components with this name to the text area will place @@ -115,10 +117,12 @@ public void actionPerformed(ActionEvent e) { blink = true; // Initialize the GUI - setLayout(new ScrollLayout()); + ScrollLayout sl = new ScrollLayout(); + setLayout(sl); add(CENTER, painter); - add(RIGHT, vertical = new JScrollBar(Adjustable.VERTICAL)); - add(BOTTOM, horizontal = new JScrollBar(Adjustable.HORIZONTAL)); + add(RIGHT, vertical = new DarkJScrollBar(Adjustable.VERTICAL)); + add(BOTTOM, horizontal = new DarkJScrollBar(Adjustable.HORIZONTAL)); + setBackground(new Color(0xff292929)); // Add some event listeners vertical.addAdjustmentListener(new AdjustHandler()); @@ -177,7 +181,14 @@ public void mouseWheelMoved(MouseWheelEvent e) { } }); } - + + + class DarkJScrollBar extends JScrollBar { + DarkJScrollBar(int typeIguess) { + super(typeIguess); + this.setUI(new DarkScrollBarUI()); + } + } /** * Override this to provide your own painter for this {@link JEditTextArea}. diff --git a/app/src/processing/app/ui/DarkScrollBarUI.java b/app/src/processing/app/ui/DarkScrollBarUI.java new file mode 100644 index 0000000000..ebff2582cb --- /dev/null +++ b/app/src/processing/app/ui/DarkScrollBarUI.java @@ -0,0 +1,46 @@ +package processing.app.ui; + +import javax.swing.*; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicScrollBarUI; +import java.awt.*; + +public class DarkScrollBarUI extends BasicScrollBarUI { + public DarkScrollBarUI() { } + + + @Override + protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { + Graphics2D g2g = (Graphics2D) g; + g2g.setColor(new Color(0xff444444)); + g2g.fill(thumbBounds); + //super.paintThumb(g, c, thumbBounds); + } + + @Override + protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) { + Graphics2D g2g = (Graphics2D) g; + g2g.setColor(new Color(0xff292929)); + g2g.fill(trackBounds); + // super.paintTrack(g, c, trackBounds); + // g2g.dispose(); + } + private JButton createZeroButton() { + JButton button = new JButton("zero button"); + Dimension zeroDim = new Dimension(0,0); + button.setPreferredSize(zeroDim); + button.setMinimumSize(zeroDim); + button.setMaximumSize(zeroDim); + return button; + } + + @Override + protected JButton createDecreaseButton(int orientation) { + return createZeroButton(); + } + + @Override + protected JButton createIncreaseButton(int orientation) { + return createZeroButton(); + } +} \ No newline at end of file diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 0e9ab769eb..3980feae85 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -40,16 +40,7 @@ import processing.app.syntax.*; import processing.core.*; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.Font; -import java.awt.Frame; -import java.awt.Image; -import java.awt.Point; -import java.awt.Window; +import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.print.*; @@ -64,6 +55,7 @@ import java.util.stream.Collectors; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.event.*; import javax.swing.plaf.basic.*; import javax.swing.text.*; @@ -91,7 +83,9 @@ public abstract class Editor extends JFrame implements RunnerListener { " " + " " + " "; - + + + /** * true if this file has not yet been given a name by the user */ @@ -148,6 +142,7 @@ public abstract class Editor extends JFrame implements RunnerListener { private final Stack caretRedoStack = new Stack<>(); private FindReplace find; + private Search search; JMenu toolsMenu; JMenu modePopup; @@ -186,6 +181,7 @@ public void windowClosing(WindowEvent e) { public void windowActivated(WindowEvent e) { base.handleActivated(Editor.this); fileMenu.insert(Recent.getMenu(), 2); + Toolkit.dark(fileMenu.getPopupMenu()); Toolkit.setMenuMnemsInside(fileMenu); mode.insertImportMenu(sketchMenu); @@ -197,6 +193,7 @@ public void windowDeactivated(WindowEvent e) { // TODO call handleActivated(null)? or do we run the risk of the // deactivate call for old window being called after the activate? fileMenu.remove(Recent.getMenu()); + Toolkit.dark(fileMenu.getPopupMenu()); mode.removeImportMenu(sketchMenu); mode.removeToolbarRecentMenu(); } @@ -447,8 +444,10 @@ public void addErrorTable(EditorFooter ef) { public EditorState getEditorState() { return state; } - - + + public void extraSearch(Search search) { } // by default don't require anything + + /** * Handles files dragged & dropped from the desktop and into the editor * window. Dragging files into the editor window is the same as using @@ -556,6 +555,7 @@ public void actionPerformed(ActionEvent e) { if (mode == m) { item.setSelected(true); } + Toolkit.dark(modePopup.getPopupMenu()); } modePopup.addSeparator(); @@ -712,10 +712,32 @@ protected void applyPreferences() { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - + + public class DarkJMenuBar extends JMenuBar { + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(new Color(0x3e4040)); + g2d.fillRect(0, 0, getWidth(), getHeight()+1); + } + + @Override public JMenu add(JMenu c) { + c.setForeground(new Color(0xbebfbf)); + Toolkit.dark(c.getPopupMenu()); + return super.add(c); + } + } protected void buildMenuBar() { - JMenuBar menubar = new JMenuBar(); + JMenuBar menubar = new DarkJMenuBar(); + menubar.setBorder(new EmptyBorder(0, 0, 1, 0) { + @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + g.setColor(new Color(0x555555)); + g.fillRect(x, height-1, width, 1); + } + }); fileMenu = buildFileMenu(); menubar.add(fileMenu); menubar.add(buildEditMenu()); @@ -976,22 +998,32 @@ public void actionPerformed(ActionEvent e) { menu.add(item); menu.addSeparator(); - + item = Toolkit.newJMenuItem(Language.text("menu.edit.find"), 'F'); item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (find == null) { - find = new FindReplace(Editor.this); - } - // https://github.com/processing/processing/issues/3457 - String selection = getSelectedText(); - if (selection != null && selection.length() != 0 && - !selection.contains("\n")) { - find.setFindText(selection); - } - find.setVisible(true); + public void actionPerformed(ActionEvent e) { + if (find == null) { + find = new FindReplace(Editor.this); } - }); + // https://github.com/processing/processing/issues/3457 + String selection = getSelectedText(); + if (selection != null && selection.length() != 0 && + !selection.contains("\n")) { + find.setFindText(selection); + } + find.setVisible(true); + } + }); + menu.add(item); + + + item = Toolkit.newJMenuItem(Language.text("menu.edit.search"), 'G'); + item.addActionListener(e -> { + if (search == null) { + search = new Search(Editor.this); + } + search.setVisible(true); + }); menu.add(item); UpdatableAction action; @@ -1102,6 +1134,7 @@ public void actionPerformed(ActionEvent e) { }); sketchMenu.add(item); menuList.add(item); + Toolkit.dark(sketchMenu.getPopupMenu()); Toolkit.setMenuMnemsInside(sketchMenu); } } @@ -2908,8 +2941,10 @@ public void statusError(Exception e) { // Make sure something is printed into the console // Status bar is volatile - System.err.println(re.getMessage()); - + // System.err.println(re.getMessage()); + re.showStackTrace(); + re.printStackTrace(); + // Move the cursor to the line before updating the status bar, otherwise // status message might get hidden by a potential message caused by moving // the cursor to a line with warning in it @@ -3292,6 +3327,7 @@ public void actionPerformed(ActionEvent e) { this.add(referenceItem); Toolkit.setMenuMnemonics(this); + Toolkit.dark(this); } // if no text is selected, disable copy and cut menu items diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java index 186e692b90..0c72efce09 100644 --- a/app/src/processing/app/ui/EditorConsole.java +++ b/app/src/processing/app/ui/EditorConsole.java @@ -42,6 +42,9 @@ import processing.app.Mode; import processing.app.Preferences; +import javax.swing.plaf.basic.*; +import java.awt.*; + /** * Message console that sits below the editing area. @@ -64,7 +67,8 @@ public class EditorConsole extends JScrollPane { PrintStream sketchErr; static EditorConsole current; - + + public EditorConsole(Editor editor) { this.editor = editor; @@ -84,6 +88,11 @@ public EditorConsole(Editor editor) { sketchErr = new PrintStream(new EditorConsoleStream(true)); startTimer(); + getVerticalScrollBar().setUI(new DarkScrollBarUI()); + getHorizontalScrollBar().setUI(new DarkScrollBarUI()); + JPanel corner = new JPanel(); + corner.setBackground(new Color(0xff292929)); + setCorner(JScrollPane.LOWER_RIGHT_CORNER, corner); } diff --git a/app/src/processing/app/ui/EditorHeader.java b/app/src/processing/app/ui/EditorHeader.java index 50fa1b8bf8..5324f8f2d8 100644 --- a/app/src/processing/app/ui/EditorHeader.java +++ b/app/src/processing/app/ui/EditorHeader.java @@ -110,7 +110,7 @@ public void mousePressed(MouseEvent e) { Sketch sketch = editor.getSketch(); for (Tab tab : tabs) { if (tab.contains(x)) { - sketch.setCurrentCode(tab.index); + sketch.setCurrentTab(tab.index); repaint(); } } @@ -132,7 +132,7 @@ public void mouseMoved(MouseEvent e) { int x = e.getX(); for (Tab tab : tabs) { if (tab.contains(x) && !tab.textVisible) { - lastNoticeName = editor.getSketch().getCode(tab.index).getPrettyName(); + lastNoticeName = editor.getSketch().getTab(tab.index).getPrettyName(); editor.statusNotice(lastNoticeName); } } @@ -198,12 +198,12 @@ public void paintComponent(Graphics screen) { g.drawImage(gradient, 0, 0, imageW, imageH, this); - if (tabs.length != sketch.getCodeCount()) { - tabs = new Tab[sketch.getCodeCount()]; + if (tabs.length != sketch.getTabCount()) { + tabs = new Tab[sketch.getTabCount()]; for (int i = 0; i < tabs.length; i++) { tabs[i] = new Tab(i); } - visitOrder = new Tab[sketch.getCodeCount() - 1]; + visitOrder = new Tab[sketch.getTabCount() - 1]; } int leftover = TAB_BETWEEN + ARROW_TAB_WIDTH; @@ -211,7 +211,7 @@ public void paintComponent(Graphics screen) { // reset all tab positions for (Tab tab : tabs) { - SketchCode code = sketch.getCode(tab.index); + SketchCode code = sketch.getTab(tab.index); tab.textVisible = true; tab.lastVisited = code.lastVisited(); @@ -291,8 +291,8 @@ private boolean placeTabs(int left, int right, Graphics2D g) { // final int top = bottom - TAB_HEIGHT; // GeneralPath path = null; - for (int i = 0; i < sketch.getCodeCount(); i++) { - SketchCode code = sketch.getCode(i); + for (int i = 0; i < sketch.getTabCount(); i++) { + SketchCode code = sketch.getTab(i); Tab tab = tabs[i]; // int pieceCount = 2 + (tab.textWidth / PIECE_WIDTH); @@ -443,7 +443,8 @@ public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } Action action; String mapKey; KeyStroke keyStroke; - + + // new tab item = Toolkit.newJMenuItemShift(Language.text("editor.header.new_tab"), KeyEvent.VK_N); action = new AbstractAction() { @Override @@ -457,7 +458,8 @@ public void actionPerformed(ActionEvent e) { actionMap.put(mapKey, action); item.addActionListener(action); menu.add(item); - + + //rename item = new JMenuItem(Language.text("editor.header.rename")); action = new AbstractAction() { @Override @@ -467,17 +469,41 @@ public void actionPerformed(ActionEvent e) { }; item.addActionListener(action); menu.add(item); - + + // close + item = Toolkit.newJMenuItemShift(Language.text("editor.header.close"), KeyEvent.VK_W); + action = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + Sketch sketch = editor.getSketch(); + if (!Platform.isMacOS() && // ok on OS X + editor.base.getEditors().size() == 1 && // mmm! accessor + sketch.getCurrentCodeIndex() == 0) { + Messages.showWarning(Language.text("editor.header.close.warning.title"), + Language.text("editor.header.close.warning.text")); + } else { + editor.getSketch().handleCloseTab(); + } + } + }; + mapKey = "editor.header.close"; + keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.SHORTCUT_KEY_MASK); + inputMap.put(keyStroke, mapKey); + actionMap.put(mapKey, action); + item.addActionListener(action); + menu.add(item); + + // delete item = Toolkit.newJMenuItemShift(Language.text("editor.header.delete"), KeyEvent.VK_D); action = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { Sketch sketch = editor.getSketch(); if (!Platform.isMacOS() && // ok on OS X - editor.base.getEditors().size() == 1 && // mmm! accessor - sketch.getCurrentCodeIndex() == 0) { - Messages.showWarning(Language.text("editor.header.delete.warning.title"), - Language.text("editor.header.delete.warning.text")); + editor.base.getEditors().size() == 1 && // mmm! accessor + sketch.getCurrentCodeIndex() == 0) { + Messages.showWarning(Language.text("editor.header.delete.warning.title"), + Language.text("editor.header.delete.warning.text")); } else { editor.getSketch().handleDeleteCode(); } @@ -531,7 +557,10 @@ public void actionPerformed(ActionEvent e) { editor.getSketch().setCurrentCode(e.getActionCommand()); } }; - for (SketchCode code : sketch.getCode()) { + list: for (SketchCode code : sketch.getCode()) { + for (SketchCode opened : sketch.getTabs()) { + if (code == opened) continue list; + } item = new JMenuItem(code.getPrettyName()); item.addActionListener(jumpListener); menu.add(item); @@ -539,6 +568,8 @@ public void actionPerformed(ActionEvent e) { } Toolkit.setMenuMnemonics(menu); + Toolkit.dark(menu.getPopupMenu()); + } diff --git a/app/src/processing/app/ui/ErrorTable.java b/app/src/processing/app/ui/ErrorTable.java index f033dfc5b0..80a95f408b 100644 --- a/app/src/processing/app/ui/ErrorTable.java +++ b/app/src/processing/app/ui/ErrorTable.java @@ -58,21 +58,15 @@ public class ErrorTable extends JTable { static final int PROBLEM_COLUMN = 1; static final int TAB_COLUMN = 2; static final int LINE_COLUMN = 3; - - Font headerFont; - Color headerColor; - Color headerBgColor; - -// Font rowFont; -// Color rowColor; -// Color rowBgColor; - - + + + + public ErrorTable(final Editor editor) { super(new DefaultTableModel(columnNames, 0)); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - + setBackground(editor.mode.getColor("errors.bgcolor")); this.editor = editor; JTableHeader header = getTableHeader(); @@ -195,7 +189,6 @@ static class GradyRowRenderer extends JLabel implements TableCellRenderer { public GradyRowRenderer(Mode mode) { setFont(mode.getFont("errors.row.font")); setAlignmentX(LEFT_ALIGNMENT); - textColor = mode.getColor("errors.row.fgcolor"); bgColor = mode.getColor("errors.row.bgcolor"); textColorSelected = mode.getColor("errors.selection.fgcolor"); diff --git a/app/src/processing/app/ui/FindReplace.java b/app/src/processing/app/ui/FindReplace.java index 757ab96251..c411a854b8 100644 --- a/app/src/processing/app/ui/FindReplace.java +++ b/app/src/processing/app/ui/FindReplace.java @@ -66,7 +66,7 @@ public class FindReplace extends JFrame { public FindReplace(Editor editor) { - super(Language.text("find")); + super(Language.text("search")); this.editor = editor; Container pain = getContentPane(); diff --git a/app/src/processing/app/ui/Search.java b/app/src/processing/app/ui/Search.java new file mode 100644 index 0000000000..98d8686e69 --- /dev/null +++ b/app/src/processing/app/ui/Search.java @@ -0,0 +1,319 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2. + + 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.app.ui; + +import processing.app.*; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; +import java.awt.*; +import java.awt.Dimension; +import java.awt.event.*; +import java.util.ArrayList; + + +/** + * Find & Replace window for the Processing editor. + */ +public class Search extends JFrame { + Editor editor; + + private static final int BORDER = Platform.isMacOS() ? 20 : 13; + + private final JList list; + private final JTextField searchField; + + + + public Search(Editor editor) { + super(Language.text("search")); + this.editor = editor; + + Container pain = getContentPane(); + + searchField = new JTextField(20); + + pain.setVisible(true); + pain.setBackground(new Color(0x252525)); + + GroupLayout layout = new GroupLayout(pain); + pain.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + + + + list = new JList<>(); + list.setVisibleRowCount(20); + list.setBackground(new Color(0x252525)); + list.setForeground(new Color(0xd2d2d2)); + + + JScrollPane listScroll = new JScrollPane(list); + listScroll.getVerticalScrollBar().setUI(new DarkScrollBarUI()); + listScroll.getHorizontalScrollBar().setUI(new DarkScrollBarUI()); + + + listScroll.setPreferredSize(list.getPreferredScrollableViewportSize()); + + + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent evt) { + int index = list.locationToIndex(evt.getPoint()); + if (index != -1) results.get(index).select(); + } + }); + + searchField.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { call(); } + public void removeUpdate(DocumentEvent e) { call(); } + public void insertUpdate(DocumentEvent e) { call(); } + + private void call() { + updateList(); + } + }); + searchField.addActionListener(e -> { + int index = list.getSelectedIndex(); + if (index != -1) results.get(index).select(); + }); + searchField.addKeyListener(new KeyListener() { + @Override public void keyTyped(KeyEvent e) { } + @Override public void keyReleased(KeyEvent e) { } + @Override public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == 65535) { + int sz = results.size(); + int index = list.getSelectedIndex(); + if (sz == 0) return; + switch (e.getKeyCode()) { + case 38: // up + list.setSelectedIndex(Math.floorMod(index-1, sz)); + break; + case 40: // down + list.setSelectedIndex(Math.floorMod(index+1, sz)); + break; + } + } + } + }); + + + list.setFont(list.getFont().deriveFont(16f)); + + + + + + layout.setHorizontalGroup(layout + .createSequentialGroup() + .addGap(BORDER) + .addGroup(layout.createParallelGroup() + .addComponent(searchField) + .addComponent(listScroll, GroupLayout.Alignment.CENTER)) + .addGap(BORDER) + ); + + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGap(BORDER) + .addComponent(searchField) + .addComponent(listScroll) + .addGap(BORDER) + ); + + setLocationRelativeTo(null); // center + Dimension size = layout.preferredLayoutSize(pain); + setSize(size.width, size.height); + Dimension screen = Toolkit.getScreenSize(); + setLocation((screen.width - size.width) / 2, + (screen.height - size.height) / 2); + + + + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + + Toolkit.registerWindowCloseKeys(getRootPane(), actionEvent -> handleClose()); + Toolkit.setIcon(this); + + addWindowListener(new WindowAdapter() { + // hack to to get first field to focus properly on osx + public void windowActivated(WindowEvent e) { + searchField.requestFocusInWindow(); + searchField.selectAll(); + } + @Override public void windowDeactivated(WindowEvent e) { + System.out.println(2);handleClose(); + } + @Override public void windowClosing(WindowEvent e) { + System.out.println(3);handleClose(); + } + }); + pack(); + setResizable(true); + setLocationRelativeTo(null); + + updateList(); + } + + private ArrayList results; + private void updateList() { + results = new ArrayList<>(); + for (SketchCode c : editor.sketch.getCode()) { + if (match(c.getFileName())) results.add(new FileSearchResult(c)); + } + + editor.extraSearch(this); + + reModel(); + } + + private void reModel() { + list.setModel(new AbstractListModel() { + @Override + public int getSize() { + return results.size(); + } + + @Override + public SearchResult getElementAt(int i) { + return results.get(i); + } + }); + + if (results.size() > 0) list.setSelectedIndex(0); + } + + public String getText() { + return searchField.getText().toLowerCase(); + } + + public void add(ArrayList names) { + results.addAll(names); + EventQueue.invokeLater(this::reModel); // idk if invokeLater is needed but i did it so + } + + class FileSearchResult extends SearchResult { + private final SketchCode tab; + + FileSearchResult(SketchCode tab) { + super(Search.this); + this.tab = tab; + } + @Override public String toString() { + return tab.getPrettyName(); + } + @Override public void select() { + handleClose(); + editor.sketch.setCurrentCode(tab); + } + } + + + public void handleClose() { + setVisible(false); + } + + + // look for the next instance of the find string to be found + // once found, select it (and go to that line) + private boolean find(boolean wrap) { + final boolean allTabs = true; + String searchTerm = searchField.getText(); + + // this will catch "find next" being called when no search yet + if (searchTerm.length() != 0) { + String text = editor.getText(); + + // Started work on find/replace across tabs. These two variables store + // the original tab and selection position so that it knew when to stop + // rotating through. + Sketch sketch = editor.getSketch(); + int tabIndex = sketch.getCurrentCodeIndex(); +// int selIndex = backwards ? +// editor.getSelectionStart() : editor.getSelectionStop(); + + + int nextIndex; + //int selectionStart = editor.textarea.getSelectionStart(); + int selectionEnd = editor.getSelectionStop(); + + nextIndex = text.indexOf(searchTerm, selectionEnd); + if (nextIndex == -1) { + // For searching in all tabs, wrapping always happens. + + int tempIndex = tabIndex; + // Look for searchterm in all tabs. + while (tabIndex <= sketch.getCodeCount() - 1) { + if (tabIndex == sketch.getCodeCount() - 1) { + // System.out.println("wrapping."); + tabIndex = -1; + } + + try { + Document doc = sketch.getCode(tabIndex + 1).getDocument(); + if(doc != null) { + text = doc.getText(0, doc.getLength()); // this thing has the latest changes + } + else { + text = sketch.getCode(tabIndex + 1).getProgram(); // not this thing. + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + tabIndex++; + text = text.toLowerCase(); + nextIndex = text.indexOf(searchTerm, 0); + + if (nextIndex != -1 || tabIndex == tempIndex) { + break; + } + } + + // searchterm wasn't found in any of the tabs. + // No tab switching should happen, restore tabIndex + if (nextIndex == -1) { + tabIndex = tempIndex; + } + } + + if (nextIndex != -1) { + sketch.setCurrentCode(tabIndex); + editor.setSelection(nextIndex, nextIndex + searchTerm.length()); + } + + return nextIndex != -1; + } + return false; + } + + + public boolean match(String str) { + return str.toLowerCase().contains(getText()); + } + + +} \ No newline at end of file diff --git a/app/src/processing/app/ui/SearchResult.java b/app/src/processing/app/ui/SearchResult.java new file mode 100644 index 0000000000..18d5fb5e5e --- /dev/null +++ b/app/src/processing/app/ui/SearchResult.java @@ -0,0 +1,15 @@ +package processing.app.ui; + +public abstract class SearchResult { + + private final Search s; + + public SearchResult(Search s) { + this.s = s; + } + + public abstract void select(); + public void close() { + s.handleClose(); + } +} diff --git a/app/src/processing/app/ui/Toolkit.java b/app/src/processing/app/ui/Toolkit.java index 866190c6ed..e46b940163 100644 --- a/app/src/processing/app/ui/Toolkit.java +++ b/app/src/processing/app/ui/Toolkit.java @@ -21,21 +21,7 @@ package processing.app.ui; -import java.awt.BasicStroke; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.FontMetrics; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Image; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Window; +import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -69,7 +55,7 @@ import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.KeyStroke; -import javax.swing.border.EmptyBorder; +import javax.swing.border.*; import processing.app.Language; import processing.app.Messages; @@ -101,7 +87,7 @@ public class Toolkit { /** Command-W on Mac OS X, Ctrl-W on Windows and Linux */ static public final KeyStroke WINDOW_CLOSE_KEYSTROKE = - KeyStroke.getKeyStroke('W', SHORTCUT_KEY_MASK); + KeyStroke.getKeyStroke('W', SHORTCUT_SHIFT_KEY_MASK); static final String BAD_KEYSTROKE = "'%s' is not understood, please re-read the Java reference for KeyStroke"; @@ -1164,4 +1150,26 @@ static public int getMenuItemIndex(JMenu menu, JMenuItem item) { } return -1; } + + + + public static void dark(JPopupMenu p) { + p.setBorder(new EmptyBorder(5, 0, 5, 0) { + @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + g.setColor(new Color(0x333333)); + g.fillRect(x, y, width, height); + } + }); + for (Component c : p.getComponents()) { + if (c instanceof JPopupMenu.Separator) { + c.setBackground(new Color(0x2B2B2B)); + ((JPopupMenu.Separator) c).setOpaque(true); + } + if (c instanceof JMenuItem) { + ((JMenuItem) c).setOpaque(true); + c.setBackground(new Color(0x393939)); + c.setForeground(new Color(0xCCCCCC)); + } + } + } } diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index d17b1c1134..dd2a64a912 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -55,6 +55,7 @@ menu.edit.decrease_indent.keystroke.macosx = meta pressed OPEN_BRACKET menu.edit.decrease_indent.keystroke.windows = ctrl pressed OPEN_BRACKET menu.edit.decrease_indent.keystroke.linux = ctrl pressed OPEN_BRACKET menu.edit.find = Find... +menu.edit.search = Search... menu.edit.find_next = Find Next menu.edit.find_previous = Find Previous menu.edit.use_selection_for_find = Use Selection for Find @@ -262,6 +263,9 @@ find.btn.replace_and_find = Replace & Find find.btn.previous = Previous find.btn.find = Find +# Search (Frame) +search = Search + # Find in reference (Frame) find_in_reference = Find in Reference @@ -327,6 +331,7 @@ toolbar.add_mode = Add Mode... # [Tab1] [Tab2] [v] editor.header.new_tab = New Tab +editor.header.close = Close editor.header.rename = Rename editor.header.delete = Delete editor.header.previous_tab = Previous Tab @@ -339,6 +344,8 @@ editor.header.next_tab.keystroke.windows = ctrl pressed PAGE_DOWN editor.header.next_tab.keystroke.linux = ctrl pressed PAGE_DOWN editor.header.delete.warning.title = Yeah, no. editor.header.delete.warning.text = You cannot delete the main tab of the only open sketch. +editor.header.close.warning.title = Nope. +editor.header.close.warning.text = You cannot close the main tab of a sketch. # PopUp menu editor.popup.jump_to_declaration = Jump to Declaration @@ -455,6 +462,10 @@ delete.messages.cannot_delete.file.description = Could not delete delete.messages.is_read_only = Sketch is Read-Only delete.messages.is_read_only.description = Some files are marked "read-only", so you will\nneed to re-save the sketch in another location,\nand try again. +# Close handler +close.messages.cannot_delete = Cannot Close +close.messages.cannot_delete.description = You cannot close tabs of an unsaved sketch. + # Save handler save_file.messages.is_read_only = Sketch is read-only save_file.messages.is_read_only.description = Some files are marked "read-only", so you will\nneed to re-save this sketch to another location. diff --git a/build/shared/lib/status/error.png b/build/shared/lib/status/error.png index 60e083677b..837af49736 100644 Binary files a/build/shared/lib/status/error.png and b/build/shared/lib/status/error.png differ diff --git a/build/shared/lib/status/notice.png b/build/shared/lib/status/notice.png index ed1b995d90..78a1a4ae92 100644 Binary files a/build/shared/lib/status/notice.png and b/build/shared/lib/status/notice.png differ diff --git a/build/shared/lib/status/warning.png b/build/shared/lib/status/warning.png index eeb52d7688..be5a1d936b 100644 Binary files a/build/shared/lib/status/warning.png and b/build/shared/lib/status/warning.png differ diff --git a/build/shared/lib/theme.txt b/build/shared/lib/theme.txt index 88083bd240..8bc542cefb 100644 --- a/build/shared/lib/theme.txt +++ b/build/shared/lib/theme.txt @@ -1,109 +1,112 @@ -# STATUS -# Status messages (1 file added to sketch, errors, etc) -status.notice.fgcolor = #000000 -status.notice.bgcolor = #818b95 -status.error.fgcolor = #ffffff -status.error.bgcolor = #9E0A0A -status.warning.bgcolor = #EF8115 -status.warning.fgcolor = #FFFFFF -status.url.fgcolor = #cccccc -status.font = processing.sans,plain,13 -# For the clipboard icon, needs to be a little larger on macOS -status.emoji.font = Dialog,plain,19 -status.emoji.font.macosx = Dialog,plain,22 - -# HEADER TABS -# Settings for the tab area at the top. -header.text.font = processing.sans,bold,14 -header.text.selected.color = #000000 -header.text.unselected.color = #ffffff -header.tab.arrow.color = #ffffff -header.gradient.top = #132638 -header.gradient.bottom = #122535 -header.tab.selected.color = #e0fffd -header.tab.unselected.color = #2d4251 -header.tab.modified.color = #ef8115 - -# FOOTER TABS -footer.text.font = processing.sans,bold,12 -footer.text.selected.color = #e0fffd -footer.text.unselected.color = #95adb0 -footer.tab.arrow.color = #ffffff -footer.gradient.top = #132638 -footer.gradient.bottom = #122535 -footer.tab.selected.color = #2d4251 -footer.tab.unselected.color = #1f3241 -# updates orange #eb7f15 -footer.updates.color = #ed7f15 - +# SKETCH TABS + header.text.font = processing.sans,bold,14 + + # text color of tabs + header.text.selected.color = #C9C9C9 + header.text.unselected.color = #C6C6C6 + header.tab.arrow.color = #ffffff + + # tab list background + header.gradient.top = #3a3c3c + header.gradient.bottom = #3a3c3c + + # tab colors (modified = that little bar saying unsaved) + header.tab.unselected.color = #444444 + header.tab.selected.color = #555555 + header.tab.modified.color = #B96900 + +# FOOTER TABS (console & errors) + footer.text.font = processing.sans,bold,12 + footer.text.selected.color = #D2D2D2 + footer.text.unselected.color = #C2C2C2 + footer.tab.arrow.color = #ffffff + footer.gradient.top = #212121 + footer.gradient.bottom = #212121 + footer.tab.selected.color = #333333 + footer.tab.unselected.color = #222222 + footer.updates.color = #ed7f15 + + +# STATUS (code & console/error separator) + status.notice.bgcolor = #333333 + status.error.bgcolor = #333333 + status.warning.bgcolor = #333333 + + status.url.fgcolor = #D2D2D2 + status.font = processing.sans,plain,13 + # CONSOLE -# The font is handled by preferences, so its size/etc are modifiable. -console.color = #000000 -console.output.color = #cccccc -# text color for errors printed in the console -console.error.color = #d9211e - -# TOOLBAR BUTTONS -buttons.bgcolor = #000000 - -# for the debug and mode buttons -#reversed.gradient.top = #10212f -#reversed.gradient.bottom = #122637 - -## size of divider between editing area and the console -#divider.size = 0 -## the larger divider on windows is ugly with the little arrows -## this makes it large enough to see (mouse changes) and use, -## but keeps it from being annoyingly obtrusive -#divider.size.windows = 2 - + # The font is handled by preferences, so its size/etc are modifiable. + console.color = #101010 + console.output.color = #D2D2D2 + # text color for errors printed in the console + console.error.color = #d9211e + +# ERRORS + # background color of the tab + errors.bgcolor = #101010 + + errors.header.font = processing.sans,plain,12 + errors.header.bgcolor = #151515 + errors.header.fgcolor = #D2D2D2 + + errors.row.font = processing.sans,plain,12 + # default colors + errors.row.bgcolor = #151515 + errors.row.fgcolor = #D2D2D2 + # error row colors + errors.selection.error.bgcolor = #151515 + errors.indicator.error.color = #d9616e + # warning row colors + errors.selection.warning.bgcolor = #151515 + errors.indicator.warning.color = #EF8115 + # selected row colors (?) + errors.selection.bgcolor = #3f3f3f + errors.selection.fgcolor = #D2D2D2 + + + +buttons.bgcolor = #FF00FF divider.height = 9 -divider.color = #CCCCCC +divider.color = #FF00FF divider.dot.diameter = 3 -divider.dot.color = #505050 +divider.dot.color = #FF00FF -# TOOLBAR BUTTON TEXT +# Run/Stop text on hover over the corresponding button toolbar.rollover.font = processing.sans,plain,12 -toolbar.rollover.color = #ffffff -toolbar.gradient.top = #142a3e -toolbar.gradient.bottom = #132638 - -# MODE SELECTOR -#mode.title.font = processing.sans,bold,15 -mode.title.font = processing.sans,plain,12 -mode.title.color = #ffffff -# outline color of the mode button -#mode.button.color = #ffffff -#mode.button.gap = 13 -#mode.arrow.width -#mode.background.color = #3D5362 -# stolen from gradient bottom -mode.background.color = #132638 -mode.outline.color = #3a505e +toolbar.rollover.color = #D2D2D2 +# toolbar with the run & stop buttons +toolbar.gradient.top = #313333 +toolbar.gradient.bottom = #313333 +# MODE SELECTOR (java, android, ect) + mode.title.font = processing.sans,plain,12 + mode.title.color = #D2D2D2 + mode.background.color = #333333 + mode.outline.color = #3232323 -# EDITOR - DETAILS -# foreground and background colors -editor.fgcolor = #000000 -editor.bgcolor = #ffffff +# EDITOR - DETAILS -editor.gradient.top = #122535 -editor.gradient.bottom = #010305 +# textfield background +editor.bgcolor = #242424 +# default text color +editor.fgcolor = #D2D2D2 +# vertical bars around the text field +editor.gradient.top = #313335 +editor.gradient.bottom = #313335 # highlight for the current line -#editor.linehighlight.color=#e2e2e2 -editor.linehighlight.color=#ebfffd -# highlight for the current line +editor.linehighlight.color=#303030 editor.linehighlight=true -editor.caret.color = #333300 -editor.selection.color = #ffcc00 +editor.caret.color = #D2D2D2 +editor.selection.color = #004488 # area that's not in use by the text (replaced with tildes) -editor.invalid.style = #7e7e7e,bold +editor.invalid.style = #FF00FF,bold -# little pooties at the end of lines that show where they finish +# trailing `.` on every line editor.eolmarkers = false editor.eolmarkers.color = #999999 @@ -111,29 +114,18 @@ editor.eolmarkers.color = #999999 editor.brackethighlight = true editor.brackethighlight.color = #006699 -editor.gutter.text.font = processing.mono,plain,11 -#editor.gutter.text.color = #657d87 -#editor.gutter.text.color = #587478 -editor.gutter.text.color = #bbd6d5 +# LINE NUMBERS + editor.gutter.text.font = processing.mono,plain,11 + editor.gutter.text.color = #AAAAAA -# marker for breakpointed lines in left hand gutter (2 ascii characters) -#editor.gutter.breakpoint.marker = <> -#editor.gutter.breakpoint.marker.color = #4a545e + # bgcolor for the current (highlighted) line + editor.gutter.linehighlight.color=#333333 -# marker for the current line in left hand gutter (2 ascii characters) -#editor.gutter.currentline.marker = -> -#editor.gutter.currentline.marker.color = #e27500 + # left- and right-hand gutter color + editor.gutter.bgcolor = #212121 -# bgcolor for the current (highlighted) line -editor.gutter.linehighlight.color=#587478 - -# left- and right-hand gutter color -editor.gutter.bgcolor = #122535 - -# color of vertical separation line -#gutter.linecolor = #e9e9e9 -# space (in px) added to left and right of gutter markers -editor.gutter.padding = 3 + # space (in px) added to left and right of gutter markers (def 3) + editor.gutter.padding = 3 # squiggly line underneath errors in the editor editor.error.underline.color = #C40E0E @@ -143,34 +135,18 @@ editor.warning.underline.color = #ffc30e editor.column.error.color = #9F1613 editor.column.warning.color = #ffc30e -# not in use? -#breakpoint.bgcolor = #f0f0f0 -#currentline.bgcolor = #ffff96 - -errors.header.font = processing.sans,plain,12 -errors.header.bgcolor = #EBEBEB -errors.header.fgcolor = #484848 -errors.row.font = processing.sans,plain,12 -errors.row.fgcolor = #484848 -errors.row.bgcolor = #FFFFFF -errors.selection.fgcolor = #242424 -errors.selection.bgcolor = #E5E5E5 -errors.selection.error.bgcolor = #F5E6E6 -errors.selection.warning.bgcolor = #FDF2E7 -#errors.indicator.size = 3 -errors.indicator.error.color = #9E0A0A -errors.indicator.warning.color = #EF8115 - -manager.tab.selected.color = #e0fffd -manager.tab.unselected.color = #2d4251 + + +manager.tab.selected.color = #444444 +manager.tab.unselected.color = #222222 manager.tab.text.font = processing.sans,bold,14 -manager.tab.text.selected.color = #000000 -manager.tab.text.unselected.color = #ffffff +manager.tab.text.selected.color = #D2D2D2 +manager.tab.text.unselected.color = #D2D2D2 # orange circle for updates manager.tab.update.color = #ed7f15 -manager.tab.gradient.top = #132638 -manager.tab.gradient.bottom = #122535 -manager.tab.background = #132638 +manager.tab.gradient.top = #333333 +manager.tab.gradient.bottom = #333333 +manager.tab.background = #333333 # tree for Examples and Sketchbook windows tree.font = processing.sans,plain,12 diff --git a/core/resource-test/scratch/test-facade.png b/core/resource-test/scratch/test-facade.png deleted file mode 100644 index 104aacb6e5..0000000000 Binary files a/core/resource-test/scratch/test-facade.png and /dev/null differ diff --git a/core/resource-test/scratch/test-facade.tga b/core/resource-test/scratch/test-facade.tga deleted file mode 100644 index e479f90fe4..0000000000 Binary files a/core/resource-test/scratch/test-facade.tga and /dev/null differ diff --git a/core/resource-test/scratch/test-facade.tiff b/core/resource-test/scratch/test-facade.tiff deleted file mode 100644 index d23f5a0061..0000000000 Binary files a/core/resource-test/scratch/test-facade.tiff and /dev/null differ diff --git a/core/resource-test/scratch/test-image-writer.png b/core/resource-test/scratch/test-image-writer.png deleted file mode 100644 index 104aacb6e5..0000000000 Binary files a/core/resource-test/scratch/test-image-writer.png and /dev/null differ diff --git a/core/resource-test/scratch/test-image-writer.tga b/core/resource-test/scratch/test-image-writer.tga deleted file mode 100644 index e479f90fe4..0000000000 Binary files a/core/resource-test/scratch/test-image-writer.tga and /dev/null differ diff --git a/core/resource-test/scratch/test-image-writer.tiff b/core/resource-test/scratch/test-image-writer.tiff deleted file mode 100644 index d23f5a0061..0000000000 Binary files a/core/resource-test/scratch/test-image-writer.tiff and /dev/null differ diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 625603e007..e9ccefedfd 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -16,6 +16,7 @@ import javax.swing.text.BadLocationException; import javax.swing.text.Document; +import org.eclipse.jdt.core.dom.SimpleName; import processing.core.PApplet; import processing.data.StringList; import processing.app.*; @@ -1098,7 +1099,7 @@ protected void handleLaunch(boolean present, boolean tweak) { if (!tweak) { runtime = jmode.handleLaunch(sketch, listener, present); } else { - runtime = jmode.handleTweak(sketch, listener); + runtime = jmode.handleTweak(sketch, listener, JavaEditor.this); } } } @@ -1816,7 +1817,6 @@ protected void downloadImports() { * Returns a list of AvailableContributions of those libraries that the user * wants imported, but that are not installed. * - * @param importHeaders */ private List getNotInstalledAvailableLibs(ArrayList importHeadersList) { Map importMap = @@ -2720,10 +2720,10 @@ protected boolean automateSketch(Sketch sketch, SketchParser parser) { int numOfInts = howManyInts(handles); int numOfFloats = howManyFloats(handles); if (numOfInts > 0) { - header += "int[] tweakmode_int = new int["+numOfInts+"];\n"; + header += "static int[] tweakmode_int = new int["+numOfInts+"];\n"; } if (numOfFloats > 0) { - header += "float[] tweakmode_float = new float["+numOfFloats+"];\n\n"; + header += "static float[] tweakmode_float = new float["+numOfFloats+"];\n\n"; } // add the server code that will receive the value change messages @@ -2735,7 +2735,8 @@ protected boolean automateSketch(Sketch sketch, SketchParser parser) { for (List list : handles) { //for (Handle n : handles[i]) { for (Handle n : list) { - header += " " + n.name + " = " + n.strValue + ";\n"; + if (n.type.equals("float")) header += " " + n.name + " = (float) " + n.strValue + ";\n"; + else header += " " + n.name + " = " + n.strValue + ";\n"; } } header += "}\n\n"; @@ -2812,4 +2813,10 @@ static private int howManyFloats(List> handles) { } return count; } + + + + public void extraSearch(Search search) { + pdex.getSearcher().search(search, search::add); + } } diff --git a/java/src/processing/mode/java/JavaMode.java b/java/src/processing/mode/java/JavaMode.java index 08b4a5a1a7..c8d3f7b965 100644 --- a/java/src/processing/mode/java/JavaMode.java +++ b/java/src/processing/mode/java/JavaMode.java @@ -140,9 +140,9 @@ public void run() { /** Start a sketch in tweak mode */ public Runner handleTweak(Sketch sketch, - RunnerListener listener) throws SketchException { + RunnerListener listener, JavaEditor javaEditor) throws SketchException { // final boolean present) throws SketchException { - final JavaEditor editor = (JavaEditor) listener; + final JavaEditor editor = javaEditor; // first try to build the unmodified code JavaBuild build = new JavaBuild(sketch); diff --git a/java/src/processing/mode/java/pdex/InspectMode.java b/java/src/processing/mode/java/pdex/InspectMode.java index 6624f6866d..80f1cf4c03 100644 --- a/java/src/processing/mode/java/pdex/InspectMode.java +++ b/java/src/processing/mode/java/pdex/InspectMode.java @@ -112,6 +112,7 @@ public void mouseWheelMoved(MouseWheelEvent e) { @Override public void keyPressed(KeyEvent e) { isHotkeyDown = isHotkeyDown || keyEventHotkeyTest.test(e); + if (e.isControlDown() && !e.isShiftDown() && e.getKeyCode() == 'B') handleInspect(); // Enable if hotkey was just pressed and mouse 1 is not down inspectModeEnabled = inspectModeEnabled || (!isMouse1Down && isHotkeyDown); } diff --git a/java/src/processing/mode/java/pdex/PDEX.java b/java/src/processing/mode/java/pdex/PDEX.java index 2a6790a7d2..5725557f5b 100644 --- a/java/src/processing/mode/java/pdex/PDEX.java +++ b/java/src/processing/mode/java/pdex/PDEX.java @@ -19,6 +19,7 @@ public class PDEX { private ShowUsage usage; private Rename rename; private DebugTree debugTree; + private Searcher searcher; private PreprocessingService pps; @@ -29,8 +30,9 @@ public PDEX(JavaEditor editor, PreprocessingService pps) { this.enabled = !editor.hasJavaTabs(); errorChecker = new ErrorChecker(editor, pps); - + usage = new ShowUsage(editor, pps); + searcher = new Searcher(editor, pps); inspect = new InspectMode(editor, pps, usage); rename = new Rename(editor, pps, usage); @@ -104,4 +106,8 @@ public void dispose() { public void documentChanged(Document newDoc) { addDocumentListener(newDoc); } + + public Searcher getSearcher() { + return searcher; + } } diff --git a/java/src/processing/mode/java/pdex/Searcher.java b/java/src/processing/mode/java/pdex/Searcher.java new file mode 100644 index 0000000000..69188aad2b --- /dev/null +++ b/java/src/processing/mode/java/pdex/Searcher.java @@ -0,0 +1,58 @@ +package processing.mode.java.pdex; + +import org.eclipse.jdt.core.dom.*; +import processing.app.ui.*; +import processing.mode.java.JavaEditor; + +import java.awt.*; +import java.util.ArrayList; +import java.util.function.Consumer; + +public class Searcher { + private final JavaEditor editor; + private final PreprocessingService pps; + + Searcher(JavaEditor editor, PreprocessingService pps) { + + this.editor = editor; + this.pps = pps; + } + public void search(Search s, Consumer> callback) { + pps.whenDoneBlocking(ps -> handleSearch(ps, s, callback)); + } + + private void handleSearch(PreprocessedSketch ps, Search search, Consumer> callback) { + ArrayList occurrences = new ArrayList<>(); + ps.compilationUnit.getRoot().accept(new ASTVisitor() { + @Override + public boolean visit(TypeDeclaration name) { + if (search.match(name.getName().getIdentifier())) occurrences.add(new SNSearchResult(search, name, ps)); + return super.visit(name); + } + }); + callback.accept(occurrences); + } + + class SNSearchResult extends SearchResult { + + private final TypeDeclaration item; + private final PreprocessedSketch ps; + + SNSearchResult(Search s, TypeDeclaration item, PreprocessedSketch ps) { + super(s); + this.item = item; + this.ps = ps; + } + + public void select() { + PreprocessedSketch.SketchInterval si = ps.mapJavaToSketch(item.getName()); + if (!ps.inRange(si)) return; + EventQueue.invokeLater(() -> editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset)); + close(); + } + + @Override public String toString() { + return item.getName().getIdentifier(); + } + } +} diff --git a/java/src/processing/mode/java/pdex/ShowUsage.java b/java/src/processing/mode/java/pdex/ShowUsage.java index 91bf043839..bf16b7ecb5 100644 --- a/java/src/processing/mode/java/pdex/ShowUsage.java +++ b/java/src/processing/mode/java/pdex/ShowUsage.java @@ -15,11 +15,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import javax.swing.JDialog; -import javax.swing.JMenuItem; -import javax.swing.JScrollPane; -import javax.swing.JTree; -import javax.swing.WindowConstants; +import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; @@ -30,9 +27,7 @@ import org.eclipse.jdt.core.dom.SimpleName; import processing.app.Language; -import processing.app.ui.EditorStatus; -import processing.app.ui.Toolkit; -import processing.app.ui.ZoomTreeCellRenderer; +import processing.app.ui.*; import processing.mode.java.JavaEditor; import processing.mode.java.pdex.PreprocessedSketch.SketchInterval; @@ -82,9 +77,19 @@ public void componentShown(ComponentEvent e) { window.setFocusableWindowState(false); Toolkit.setIcon(window); JScrollPane sp2 = new JScrollPane(); + + sp2.setBorder(new EmptyBorder(0, 0, 0, 0)); + sp2.getVerticalScrollBar().setUI(new DarkScrollBarUI()); + sp2.getHorizontalScrollBar().setUI(new DarkScrollBarUI()); + JPanel corner = new JPanel(); + corner.setBackground(new Color(0xff292929)); + sp2.setCorner(JScrollPane.LOWER_RIGHT_CORNER, corner); + tree = new JTree(); + tree.setBackground(new Color(0x222222)); ZoomTreeCellRenderer renderer = new ZoomTreeCellRenderer(editor.getMode()); + renderer.setTextNonSelectionColor(new Color(0xD2D2D2)); tree.setCellRenderer(renderer); renderer.setLeafIcon(null); renderer.setClosedIcon(null); @@ -211,7 +216,7 @@ void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding binding) { String usageLabel = count == 1 ? "usage" : "usages"; // Create new DefaultMutableTreeNode for this tab - String tabLabel = "" + + String tabLabel = "" + ps.sketch.getCode(tabIndex).getPrettyName() + " " + count + " " + usageLabel + ""; DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel); @@ -312,12 +317,12 @@ static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterva .replace("&", "&").replace(">", ">").replace("<", "<"); String post = line.substring(highlightStopOffset) .replace("&", "&").replace(">", ">").replace("<", "<"); - line = pre + "" + highlight + "" + post; + line = pre + "" + highlight + "" + post; line = line.trim(); - String text = "" + - (tabLine + 1) + " " + line + ""; + String text = "" + + (tabLine + 1) + " " + line + ""; return new ShowUsageTreeNode(in.tabIndex, in.startTabOffset, in.stopTabOffset, text); } diff --git a/java/src/processing/mode/java/tweak/SketchParser.java b/java/src/processing/mode/java/tweak/SketchParser.java index d2a85e0504..d0f947b532 100644 --- a/java/src/processing/mode/java/tweak/SketchParser.java +++ b/java/src/processing/mode/java/tweak/SketchParser.java @@ -157,8 +157,8 @@ private void addAllDecimalNumbers() { continue; } - // remove any 'f' after the number - if (c.charAt(end) == 'f') { + // remove any 'f' or 'd' after the number (let's hope there aren't too huge double constants) + if (c.charAt(end) == 'f' || c.charAt(end) == 'd') { forceFloat = true; end++; } diff --git a/java/src/processing/mode/java/tweak/TweakClient.java b/java/src/processing/mode/java/tweak/TweakClient.java index 517d9c2a94..3ce67847eb 100644 --- a/java/src/processing/mode/java/tweak/TweakClient.java +++ b/java/src/processing/mode/java/tweak/TweakClient.java @@ -139,7 +139,7 @@ static public String getServerCode(int listenPort, " socket = new DatagramSocket("+listenPort+");\n"+ " socket.setSoTimeout(250);\n"+ " } catch (IOException e) {\n"+ - " addEmptyLine(\"error: could not create TweakMode server socket\");\n"+ + " System.err.println(\"error: could not create TweakMode server socket\");\n"+ " }\n"+ " }\n"+ " public void run()\n"+