EOS 2  1.1.0
Einfache Objektbasierte Sprache
AutoCompletion.java
gehe zur Dokumentation dieser Datei
1 package de.lathanda.eos.gui;
2 
3 import static de.lathanda.eos.gui.icons.Icons.*;
4 
5 import java.awt.Color;
6 import java.awt.Component;
7 import java.awt.Dimension;
8 import java.awt.Font;
9 import java.awt.Rectangle;
10 import java.awt.event.ComponentEvent;
11 import java.awt.event.ComponentListener;
12 import java.awt.event.FocusEvent;
13 import java.awt.event.FocusListener;
14 import java.awt.event.KeyEvent;
15 import java.awt.event.KeyListener;
16 import java.awt.event.MouseAdapter;
17 import java.awt.event.MouseEvent;
18 import java.awt.geom.Rectangle2D;
19 import java.util.Set;
20 import java.util.TreeSet;
21 
22 import javax.swing.ImageIcon;
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JList;
26 import javax.swing.JScrollPane;
27 import javax.swing.JWindow;
28 import javax.swing.ListCellRenderer;
29 import javax.swing.ListSelectionModel;
30 import javax.swing.ToolTipManager;
31 import javax.swing.event.CaretEvent;
32 import javax.swing.event.CaretListener;
33 import javax.swing.text.BadLocationException;
34 import javax.swing.text.Document;
35 import javax.swing.text.AbstractDocument;
36 import javax.swing.text.JTextComponent;
37 
38 import de.lathanda.eos.base.util.GuiToolkit;
39 import de.lathanda.eos.baseparser.AbstractProgram;
40 import de.lathanda.eos.baseparser.AutoCompleteEntry;
41 import de.lathanda.eos.baseparser.AutoCompleteInformation;
42 import de.lathanda.eos.baseparser.AutoCompleteType;
43 import de.lathanda.eos.common.gui.AutoCompleteHook;
44 import de.lathanda.eos.config.Language;
52 public class AutoCompletion implements CaretListener, KeyListener, FocusListener, ComponentListener, AutoCompleteHook {
53  public final static ImageIcon[] ICON = {
55  GuiToolkit.createSmallIcon(PROPERTY),
57  GuiToolkit.createSmallIcon(PROPERTY),
58  GuiToolkit.createSmallIcon(ELEMENT),
60  };
64  private int lastPosition = -1;
68  private int startPosition = -1;
72  private boolean active = false;
76  private JWindow choiceWindow;
80  private TreeSet<AutoCompleteInformation> choiceItems = new TreeSet<>();
84  private boolean consumeNextKey = false;
88  private final TooltipList choiceList;
92  private final JTextComponent component;
93 
94  public AutoCompletion(JTextComponent component, JFrame mainWindow) {
95  this.component = component;
96  choiceWindow = new JWindow();
97  choiceWindow.setFocusable(false);
98  choiceList = new TooltipList();
99  choiceList.setFont(GuiToolkit.createFont(Font.MONOSPACED, Font.PLAIN, 10));
100  choiceList.setCellRenderer(new ChoiceCellRenderer());
101  choiceList.setEnabled(true);
102  choiceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
103  choiceList.addMouseListener(new MouseAdapter() {
104  public void mouseClicked(MouseEvent e) {
105  if (e.getClickCount() == 2) {
106  complete();
107  }
108  }
109  });
110  JScrollPane choiceScroll = new JScrollPane(choiceList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
111  JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
112  choiceWindow.getContentPane().add(choiceScroll);
113  component.addCaretListener(this);
114  component.addKeyListener(this);
115  component.addFocusListener(this);
116  mainWindow.addComponentListener(this);
117  component.addComponentListener(this);
118  }
119 
126  public void start(AutoCompleteType base, int position) throws BadLocationException {
127  if (base == null || base.isUnknown()) {
128  stop();
129  return;
130  }
131  startPosition = position;
132  lastPosition = position;
133  if (Language.def.isLockProperties()) {
134  for (AutoCompleteInformation aci : base.getAutoCompletes()) {
135  if (aci.getType() != AutoCompleteInformation.PRIVATE
136  && aci.getType() != AutoCompleteInformation.PROPERTY) {
137  choiceItems.add(aci);
138  }
139  }
140  } else {
141  for (AutoCompleteInformation aci : base.getAutoCompletes()) {
142  if (aci.getType() != AutoCompleteInformation.PRIVATE) {
143  choiceItems.add(aci);
144  }
145  }
146  }
147  showMenue();
148  }
149 
156  public void startClass(AbstractProgram program, int position) throws BadLocationException {
157  if (program == null)
158  return;
159  startPosition = position;
160  lastPosition = position;
161  for (AutoCompleteInformation aci : program.getClassAutoCompletes()) {
162  if (aci != null && aci.getType() != AutoCompleteInformation.PRIVATE) {
163  choiceItems.add(aci);
164  }
165  }
166  showMenue();
167  }
168 
172  private void showMenue() {
173  if (choiceItems.size() == 0) {
174  stop();
175  return;
176  }
177  updateChoices();
178  setPosition();
179  active = true;
180 
181  choiceWindow.setPreferredSize(new Dimension(200, 200));
182  Dimension listDimension = choiceList.getPreferredSize();
183  choiceWindow.setSize(listDimension.width + 20, Math.min(listDimension.height + 10, 400));
184  choiceWindow.setVisible(true);
185  component.requestFocus();
186  }
187 
191  public void startTemplate() {
192  int position = component.getCaretPosition();
193  startPosition = position;
194  lastPosition = position;
195  Set<AutoCompleteEntry> templates = Language.def.getTemplates();
196  for (AutoCompleteEntry entry : templates) {
197  choiceItems.add(entry);
198  }
199  showMenue();
200  }
201 
205  private void stop() {
206  active = false;
207  choiceItems.clear();
208  choiceWindow.setVisible(false);
209  choiceList.removeAll();
210  }
211 
216  private void setPosition() {
217  Rectangle2D box;
218  try {
219  box = component.modelToView2D(startPosition - 1);
220  } catch (BadLocationException e) {
221  box = new Rectangle(0, 0);
222  }
223 
224  int windowX = component.getLocationOnScreen().x + (int) box.getX();
225  int windowY = component.getLocationOnScreen().y + (int) (box.getY() + box.getHeight());
226  choiceWindow.setLocation(windowX, windowY);
227  }
228 
232  private void complete() {
233  AutoCompleteInformation choice = choiceList.getSelectedValue();
234  Document text = component.getDocument();
235  stop(); // stop before changing in order to avoid feedback
236  if (choice != null) {
237  try {
238  ((AbstractDocument) text).replace(startPosition, lastPosition - startPosition, choice.getTemplate(),
239  null);
240  int openBracket = startPosition + choice.getTemplate().indexOf("(") + 1;
241  if (openBracket != startPosition) {
242  component.setCaretPosition(openBracket);
243  }
244  } catch (BadLocationException e) {
245  // we can't do anything useful, but nothing
246  }
247  }
248  }
249 
253  private void updateChoices() {
254  Document text = component.getDocument();
255  try {
256  AutoCompleteInformation selected = choiceList.getSelectedValue();
257  String prefix = text.getText(startPosition, lastPosition - startPosition).toLowerCase();
258  AutoCompleteInformation[] choices = choiceItems.stream()
259  .filter(choice -> choice.getScantext().toLowerCase().startsWith(prefix))
260  .toArray(size -> new AutoCompleteInformation[size]);
261  if (choices.length == 0) {
262  stop();
263  } else {
264  choiceList.setListData(choices);
265  if (selected != null) {
266  choiceList.setSelectedValue(selected, true);
267  if (choiceList.getSelectedValue() == null) {
268  choiceList.setSelectedIndex(0);
269  }
270  } else {
271  choiceList.setSelectedIndex(0);
272  }
273  }
274  } catch (BadLocationException e) {
275  stop();
276  return;
277  }
278  }
279 
284  private void setSelectedItem(int index) {
285  if (choiceList.getModel().getSize() == 0)
286  return;
287  int n = index % choiceList.getModel().getSize();
288  if (n < 0) {
289  n += choiceList.getModel().getSize();
290  }
291  choiceList.setSelectedIndex(n);
292  choiceList.ensureIndexIsVisible(n);
293 
294  }
295 
296  private class TooltipList extends JList<AutoCompleteInformation> {
297  private static final long serialVersionUID = -7313420722740426372L;
298 
299  public TooltipList() {
300  ToolTipManager.sharedInstance().registerComponent(this);
301  }
302 
303  @Override
304  public String getToolTipText(MouseEvent me) {
305  int index = locationToIndex(me.getPoint());
306  if (index >= 0) {
307  return "<html><p>" + getModel().getElementAt(index).getTooltip() + "</p></html>";
308  } else {
309  return null;
310  }
311  }
312  }
313 
319  private static class ChoiceCellRenderer extends JLabel implements ListCellRenderer<AutoCompleteInformation> {
320  private static final long serialVersionUID = -6215568900839124763L;
321 
322  @Override
323  public Component getListCellRendererComponent(JList<? extends AutoCompleteInformation> list,
324  AutoCompleteInformation value, int index, boolean isSelected, boolean cellHasFocus) {
325  setText(value.getLabel());
326  setIcon(ICON[value.getType()]);
327  if (isSelected) {
328  setBackground(Color.BLUE);
329  setForeground(Color.WHITE);
330  } else {
331  setBackground(Color.WHITE);
332  setForeground(Color.BLACK);
333  }
334  setEnabled(list.isEnabled());
335  setFont(list.getFont());
336  setOpaque(true);
337  return this;
338  }
339 
340  }
341 
342  @Override
343  public void caretUpdate(CaretEvent ce) {
344  if (!active)
345  return;
346  lastPosition = ce.getDot();
347  if (lastPosition < startPosition) {
348  stop();
349  } else {
350  updateChoices();
351  }
352  }
353 
354  @Override
355  public void keyPressed(KeyEvent ke) {
356  if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_SPACE) {
357  ke.consume();
358  startTemplate();
359  consumeNextKey = true;
360  return;
361  }
362  if (!active)
363  return;
364 
365  switch (ke.getKeyCode()) {
366  case KeyEvent.VK_UP:
367  setSelectedItem(choiceList.getSelectedIndex() - 1);
368  ke.consume();
369  break;
370  case KeyEvent.VK_DOWN:
371  setSelectedItem(choiceList.getSelectedIndex() + 1);
372  ke.consume();
373  break;
374  case KeyEvent.VK_ENTER:
375  case KeyEvent.VK_TAB:
376  complete();
377  ke.consume();
378  case KeyEvent.VK_ESCAPE:
379  stop();
380  ke.consume();
381  }
382 
383  }
384 
385  @Override
386  public void keyReleased(KeyEvent ke) {
387  }
388 
389  @Override
390  public void keyTyped(KeyEvent ke) {
391  if (!active)
392  return;
393  if (consumeNextKey) {
394  consumeNextKey = false;
395  ke.consume();
396  return;
397  }
398  // any none alphabetic character will stop code completion
399  if (!(Character.isAlphabetic(ke.getKeyChar()) || ke.getKeyChar() == '\b')) {
400  stop();
401  }
402  }
403 
404  @Override
405  public void focusGained(FocusEvent fe) {
406  }
407 
408  @Override
409  public void focusLost(FocusEvent fe) {
410  if (!active)
411  return;
412  stop();
413  }
414 
415  @Override
416  public void componentHidden(ComponentEvent arg0) {
417  if (!active)
418  return;
419  stop();
420  }
421 
422  @Override
423  public void componentMoved(ComponentEvent ce) {
424  if (!active)
425  return;
426  setPosition();
427  }
428 
429  @Override
430  public void componentResized(ComponentEvent ce) {
431  if (!active)
432  return;
433  setPosition();
434  }
435 
436  @Override
437  public void componentShown(ComponentEvent ce) {
438  }
439 
440  @Override
441  public void insertString(int pos, String text, AbstractProgram program) {
442  try {
443  if (text.equals(".")) {
444  AutoCompleteType base = program.seekType(pos);
445  start(base, pos + 1);
446  } else if (text.equals(":")) {
447  startClass(program, pos + 1);
448  }
449  } catch (BadLocationException e) {
450  // ignore it
451  }
452  }
453 }
static ImageIcon createSmallIcon(String image)
static Font createFont(String name, int style, int size)
Definition: GuiToolkit.java:89
Set< AutoCompleteEntry > getTemplates()
Definition: Language.java:183
void componentMoved(ComponentEvent ce)
void componentHidden(ComponentEvent arg0)
static final ImageIcon[] ICON
void startClass(AbstractProgram program, int position)
void componentResized(ComponentEvent ce)
void start(AutoCompleteType base, int position)
void componentShown(ComponentEvent ce)
void insertString(int pos, String text, AbstractProgram program)
AutoCompletion(JTextComponent component, JFrame mainWindow)
AutoCompleteType seekType(int pos)
Impressum