processing.mode.java.pdex.CompletionPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-mode Show documentation
Show all versions of java-mode Show documentation
Processing is a programming language, development environment, and online community.
This Java Mode package contains the Java mode for Processing IDE.
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 The Processing Foundation
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.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package processing.mode.java.pdex;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.text.BadLocationException;
import processing.app.Base;
import processing.app.Messages;
import processing.app.Mode;
import processing.app.syntax.JEditTextArea;
import processing.app.ui.Toolkit;
import processing.mode.java.JavaEditor;
public class CompletionPanel {
/**
* The completion list generated by ASTGenerator
*/
private JList completionList;
/**
* The popup menu in which the suggestion list is shown
*/
private JPopupMenu popupMenu;
/**
* Partial word which triggered the code completion and which needs to be completed
*/
private String subWord;
/**
* Position where the completion has to be inserted
*/
private int insertionPosition;
private JavaTextArea textarea;
/**
* Scroll pane in which the completion list is displayed
*/
private JScrollPane scrollPane;
protected JavaEditor editor;
static protected final int MOUSE_COMPLETION = 10, KEYBOARD_COMPLETION = 20;
private boolean horizontalScrollBarVisible = false;
static public ImageIcon classIcon;
static public ImageIcon fieldIcon;
static public ImageIcon methodIcon;
static public ImageIcon localVarIcon;
static Color selectionBgColor;
static Color textColor;
/**
* Triggers the completion popup
* @param textarea
* @param position - insertion position(caret pos)
* @param subWord - Partial word which triggered the code completion and which needs to be completed
* @param items - completion candidates
* @param location - Point location where popup list is to be displayed
* @param dedit
*/
public CompletionPanel(final JEditTextArea textarea,
int position, String subWord,
DefaultListModel items,
final Point location, JavaEditor editor) {
this.textarea = (JavaTextArea) textarea;
this.editor = editor;
this.insertionPosition = position;
if (subWord.indexOf('.') != -1 && subWord.indexOf('.') != subWord.length()-1) {
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
} else {
this.subWord = subWord;
}
if (classIcon == null) {
Mode mode = editor.getMode();
File dir = new File(mode.getFolder(), "theme/completion");
classIcon = Toolkit.getIconX(dir, "class_obj");
methodIcon = Toolkit.getIconX(dir, "methpub_obj");
fieldIcon = Toolkit.getIconX(dir, "field_protected_obj");
localVarIcon = Toolkit.getIconX(dir, "field_default_obj");
//selectionBgColor = mode.getColor(""); // no theme.txt for Java Mode
selectionBgColor = new Color(0xffF0F0F0);
textColor = new Color(0xff222222);
}
popupMenu = new JPopupMenu();
popupMenu.removeAll();
popupMenu.setOpaque(false);
popupMenu.setBorder(null);
scrollPane = new JScrollPane();
// styleScrollPane();
//scrollPane.setViewportView(completionList = createSuggestionList(position, items));
completionList = new JList(items) {
{
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setSelectedIndex(0);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
insertSelection(MOUSE_COMPLETION);
setInvisible();
}
}
});
setCellRenderer(new CustomListRenderer());
setFocusable(false);
setFont(Toolkit.getSansFont(12, Font.PLAIN));
}
};
scrollPane.setViewportView(completionList);
// remove an ugly multi-line border around it
scrollPane.setBorder(null);
popupMenu.add(scrollPane, BorderLayout.CENTER);
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize())); //TODO: Eradicate this evil
popupMenu.setFocusable(false);
// TODO: Update JavaDoc to completionList.getSelectedValue()
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + location.y);
//log("Suggestion shown: " + System.currentTimeMillis());
}
/*
private void styleScrollPane() {
String laf = UIManager.getLookAndFeel().getID();
if (!laf.equals("Nimbus") && !laf.equals("Windows")) return;
String thumbColor = null;
if (laf.equals("Nimbus")) {
UIDefaults defaults = new UIDefaults();
defaults.put("PopupMenu.contentMargins", new InsetsUIResource(0, 0, 0, 0));
defaults.put("ScrollPane[Enabled].borderPainter", new Painter() {
public void paint(Graphics2D g, JComponent t, int w, int h) {}
});
popupMenu.putClientProperty("Nimbus.Overrides", defaults);
scrollPane.putClientProperty("Nimbus.Overrides", defaults);
thumbColor = "nimbusBlueGrey";
} else if (laf.equals("Windows")) {
thumbColor = "ScrollBar.thumbShadow";
}
scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(Integer.MAX_VALUE, 8));
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(8, Integer.MAX_VALUE));
scrollPane.getHorizontalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
scrollPane.getVerticalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
}
private static class CompletionScrollBarUI extends BasicScrollBarUI {
private String thumbColorName;
protected CompletionScrollBarUI(String thumbColorName) {
this.thumbColorName = thumbColorName;
}
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle trackBounds) {
g.setColor((Color) UIManager.get(thumbColorName));
g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
}
@Override
protected JButton createDecreaseButton(int orientation) {
return createZeroButton();
}
@Override
protected JButton createIncreaseButton(int orientation) {
return createZeroButton();
}
static private JButton createZeroButton() {
JButton jbutton = new JButton();
jbutton.setPreferredSize(new Dimension(0, 0));
jbutton.setMinimumSize(new Dimension(0, 0));
jbutton.setMaximumSize(new Dimension(0, 0));
return jbutton;
}
}
*/
public boolean isVisible() {
return popupMenu.isVisible();
}
public void setInvisible() {
popupMenu.setVisible(false);
}
/**
* Dynamic height of completion panel depending on item count
*/
private int calcHeight(int itemCount) {
int maxHeight = 250;
FontMetrics fm = textarea.getGraphics().getFontMetrics();
float itemHeight = Math.max((fm.getHeight() + (fm.getDescent()) * 0.5f),
classIcon.getIconHeight() * 1.2f);
if (horizontalScrollBarVisible) {
itemCount++;
}
if (itemCount < 4) {
itemHeight *= 1.3f; //Sorry, but it works.
}
float h = itemHeight * (itemCount);
if (itemCount >= 4) {
h += itemHeight * 0.3; // a bit of offset
}
return Math.min(maxHeight, (int) h); // popup menu height
}
private int calcWidth() {
int maxWidth = 300;
float min = 0;
FontMetrics fm = textarea.getGraphics().getFontMetrics();
for (int i = 0; i < completionList.getModel().getSize(); i++) {
float h = fm.stringWidth(completionList.getModel().getElementAt(i).getLabel());
min = Math.max(min, h);
}
int w = Math.min((int) min, maxWidth);
horizontalScrollBarVisible = (w == maxWidth);
w += classIcon.getIconWidth(); // add icon width too!
w += fm.stringWidth(" "); // a bit of offset
//log("popup width " + w);
return w; // popup menu width
}
/**
* Created the popup list to be displayed
* @param position
* @param items
* @return
private JList createSuggestionList(final int position,
final DefaultListModel items) {
JList list = new JList(items);
//list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
insertSelection(MOUSE_COMPLETION);
setInvisible();
}
}
});
list.setCellRenderer(new CustomListRenderer());
list.setFocusable(false);
return list;
}
*/
/*
// possibly defunct
private boolean updateList(final DefaultListModel items, String newSubword,
final Point location, int position) {
this.subWord = new String(newSubword);
if (subWord.indexOf('.') != -1)
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
insertionPosition = position;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
scrollPane.getViewport().removeAll();
completionList.setModel(items);
completionList.setSelectedIndex(0);
scrollPane.setViewportView(completionList);
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize()));
//log("Suggestion updated" + System.nanoTime());
textarea.requestFocusInWindow();
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
+ location.y);
completionList.validate();
scrollPane.validate();
popupMenu.validate();
}
});
return true;
}
*/
/**
* Inserts the CompletionCandidate chosen from the suggestion list
* @param completionSource - whether being completed via keypress or mouse click.
* @return true - if code was successfully inserted at the caret position
*/
protected boolean insertSelection(int completionSource) {
if (completionList.getSelectedValue() != null) {
try {
// If user types 'abc.', subword becomes '.' and null is returned
String currentSubword = fetchCurrentSubword();
int currentSubwordLen =
(currentSubword == null) ? 0 : currentSubword.length();
//logE(currentSubword + " <= subword,len => " + currentSubword.length());
String selectedSuggestion =
completionList.getSelectedValue().getCompletionString();
if (currentSubword != null) {
selectedSuggestion = selectedSuggestion.substring(currentSubwordLen);
} else {
currentSubword = "";
}
String completionString =
completionList.getSelectedValue().getCompletionString();
if (selectedSuggestion.endsWith(" )")) { // the case of single param methods
// selectedSuggestion = ")";
if (completionString.endsWith(" )")) {
completionString =
completionString.substring(0, completionString.length() - 2) + ")";
}
}
boolean mouseClickOnOverloadedMethods = false;
if (completionSource == MOUSE_COMPLETION) {
// The case of overloaded methods, displayed as 'foo(...)'
// They have completion strings as 'foo('. See #2755
if (completionString.endsWith("(")) {
mouseClickOnOverloadedMethods = true;
}
}
Messages.loge(subWord + " <= subword, Inserting suggestion=> " +
selectedSuggestion + " Current sub: " + currentSubword);
if (currentSubword.length() > 0) {
textarea.getDocument().remove(insertionPosition - currentSubwordLen,
currentSubwordLen);
}
textarea.getDocument().insertString(insertionPosition - currentSubwordLen,
completionString, null);
if (selectedSuggestion.endsWith(")") && !selectedSuggestion.endsWith("()")) {
// place the caret between '( and first ','
int x = selectedSuggestion.indexOf(',');
if (x == -1) {
// the case of single param methods, containing no ','
textarea.setCaretPosition(textarea.getCaretPosition() - 1); // just before ')'
} else {
textarea.setCaretPosition(insertionPosition + x);
}
}
Messages.log("Suggestion inserted: " + System.currentTimeMillis());
if (completionList.getSelectedValue().getLabel().contains("...")) {
// log("No hide");
// Why not hide it? Coz this is the case of
// overloaded methods. See #2755
} else {
setInvisible();
}
if (mouseClickOnOverloadedMethods) {
// See #2755
((JavaTextArea) editor.getTextArea()).fetchPhrase();
}
return true;
} catch (BadLocationException e1) {
e1.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
setInvisible();
}
return false;
}
private String fetchCurrentSubword() {
//log("Entering fetchCurrentSubword");
JEditTextArea ta = editor.getTextArea();
int off = ta.getCaretPosition();
//log2("off " + off);
if (off < 0)
return null;
int line = ta.getCaretLine();
if (line < 0)
return null;
String s = ta.getLineText(line);
//log2("lin " + line);
//log2(s + " len " + s.length());
int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1;
if (x >= s.length() || x < 0)
return null; //TODO: Does this check cause problems? Verify.
if (Base.DEBUG) System.out.print(" x char: " + s.charAt(x));
//int xLS = off - getLineStartNonWhiteSpaceOffset(line);
String word = (x < s.length() ? s.charAt(x) : "") + "";
if (s.trim().length() == 1) {
// word = ""
// + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
//word = (x < s.length()?s.charAt(x):"") + "";
word = word.trim();
if (word.endsWith("."))
word = word.substring(0, word.length() - 1);
return word;
}
//log("fetchCurrentSubword 1 " + word);
if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.'
// if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE)
// ; // accepted these keys
// else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$'))
// return null;
int i = 0;
while (true) {
i++;
//TODO: currently works on single line only. "a. b()" won't be detected
if (x1 >= 0) {
// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_') {
word = s.charAt(x1--) + word;
} else {
break;
}
} else {
break;
}
if (i > 200) {
// time out!
break;
}
}
// if (keyChar != KeyEvent.CHAR_UNDEFINED)
//log("fetchCurrentSubword 2 " + word);
if (Character.isDigit(word.charAt(0)))
return null;
word = word.trim();
if (word.endsWith("."))
word = word.substring(0, word.length() - 1);
//log("fetchCurrentSubword 3 " + word);
//showSuggestionLater();
return word;
//}
}
/**
* When up arrow key is pressed, moves the highlighted selection up in the list
*/
protected void moveUp() {
if (completionList.getSelectedIndex() == 0) {
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
selectIndex(completionList.getModel().getSize() - 1);
} else {
int index = Math.max(completionList.getSelectedIndex() - 1, 0);
selectIndex(index);
int step = scrollPane.getVerticalScrollBar().getMaximum()
/ completionList.getModel().getSize();
scrollPane.getVerticalScrollBar().setValue(scrollPane
.getVerticalScrollBar()
.getValue()
- step);
// TODO: update JavaDoc to completionList.getSelectedValue()
}
}
/**
* When down arrow key is pressed, moves the highlighted selection down in the list
*/
protected void moveDown() {
if (completionList.getSelectedIndex() == completionList.getModel().getSize() - 1) {
scrollPane.getVerticalScrollBar().setValue(0);
selectIndex(0);
} else {
int index = Math.min(completionList.getSelectedIndex() + 1,
completionList.getModel().getSize() - 1);
selectIndex(index);
// TODO: update JavaDoc to completionList.getSelectedValue()
int step = scrollPane.getVerticalScrollBar().getMaximum() / completionList.getModel().getSize();
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + step);
}
}
private void selectIndex(int index) {
completionList.setSelectedIndex(index);
}
/**
* Custom cell renderer to display icons along with the completion candidates
* @author Manindra Moharana
*
*/
private static class CustomListRenderer extends javax.swing.DefaultListCellRenderer {
public Component getListCellRendererComponent(JList> list, Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value,
index,
isSelected,
cellHasFocus);
if (isSelected) {
label.setBackground(selectionBgColor);
}
label.setForeground(textColor);
if (value instanceof CompletionCandidate) {
CompletionCandidate cc = (CompletionCandidate) value;
switch (cc.getType()) {
case CompletionCandidate.LOCAL_VAR:
label.setIcon(localVarIcon);
break;
case CompletionCandidate.LOCAL_FIELD:
case CompletionCandidate.PREDEF_FIELD:
label.setIcon(fieldIcon);
break;
case CompletionCandidate.LOCAL_METHOD:
case CompletionCandidate.PREDEF_METHOD:
label.setIcon(methodIcon);
break;
case CompletionCandidate.LOCAL_CLASS:
case CompletionCandidate.PREDEF_CLASS:
label.setIcon(classIcon);
break;
default:
Messages.log("(CustomListRenderer)Unknown CompletionCandidate type " + cc.getType());
break;
}
} else {
Messages.log("(CustomListRenderer)Unknown CompletionCandidate object " + value);
}
return label;
}
}
}