Completion: fix after ffe3702fc
[scilab.git] / scilab / modules / completion / src / java / org / scilab / modules / completion / AbstractSciCompletionWindow.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2007-2008 - INRIA - Vincent COUVERT
4  * Copyright (C) 2010-2011 - Calixte DENIZET
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16
17 package org.scilab.modules.completion;
18
19 import java.awt.BorderLayout;
20 import java.awt.Component;
21 import java.awt.Cursor;
22 import java.awt.Dimension;
23 import java.awt.Point;
24 import java.awt.event.FocusEvent;
25 import java.awt.event.FocusListener;
26 import java.awt.event.KeyEvent;
27 import java.awt.event.KeyListener;
28 import java.awt.event.MouseEvent;
29 import java.awt.event.MouseListener;
30 import java.awt.event.MouseMotionListener;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34
35 import javax.swing.AbstractListModel;
36 import javax.swing.JComponent;
37 import javax.swing.text.JTextComponent;
38 import javax.swing.JLabel;
39 import javax.swing.JList;
40 import javax.swing.JPanel;
41 import javax.swing.JScrollPane;
42
43 import com.artenum.rosetta.ui.CompletionItemListCellRenderer;
44 import com.artenum.rosetta.interfaces.core.CompletionItem;
45 import com.artenum.rosetta.interfaces.core.InputParsingManager;
46 import com.artenum.rosetta.interfaces.ui.CompletionWindow;
47
48 import org.scilab.modules.completion.Completion;
49 import org.scilab.modules.localization.Messages;
50
51 /**
52  * Scilab completion window main class
53  * @author Vincent COUVERT
54  * @author Calixte DENIZET
55  */
56 public abstract class AbstractSciCompletionWindow implements CompletionWindow, KeyListener, FocusListener, MouseMotionListener, MouseListener {
57
58     private static final int WINDOW_WIDTH = 300;
59     private static final int WINDOW_HEIGHT = 100;
60
61     protected JPanel window;
62     protected CompletionItemListModel model;
63     protected JList listUI;
64     protected JScrollPane scrollPane;
65     protected InputParsingManager inputParsingManager;
66     protected JComponent focusOutComponent;
67
68     protected int currentCaretPosition;
69
70     /**
71      * Default constructor
72      */
73     public AbstractSciCompletionWindow() {
74     }
75
76     /**
77      * @return the JTextComponent where the CompletionWindow will be drawn
78      */
79     public abstract JTextComponent getTextComponent();
80
81     /**
82      * @return the list of the possibilities to complete
83      */
84     public abstract List<CompletionItem> getCompletionItems();
85
86     /**
87      * Set the component that should take the focus when the completion window is hidden
88      * @param component the component to focus
89      * @see com.artenum.rosetta.interfaces.ui.CompletionWindow#setFocusOut(javax.swing.JComponent)
90      */
91     @Override
92     public void setFocusOut(JComponent component) {
93         focusOutComponent = component;
94     }
95
96     /**
97      * Set the input parsing manager associated to this completion window
98      * @param inputParsingManager the input parsing manager to set
99      * @see com.artenum.rosetta.interfaces.ui.CompletionWindow#setInputParsingManager(com.artenum.rosetta.interfaces.core.InputParsingManager)
100      */
101     @Override
102     public void setInputParsingManager(InputParsingManager inputParsingManager) {
103         this.inputParsingManager = inputParsingManager;
104     }
105
106     /**
107      * Set the parent component for the completion window
108      * @param component the parent component
109      * Caution, the component shouldn't be null otherwise the completion window
110      * will never get the focus
111      */
112     @Override
113     public void setGraphicalContext(Component component) {
114
115         /* List to display all completion items */
116         model = new CompletionItemListModel();
117         listUI = new JList(model);
118         listUI.setCellRenderer(new CompletionItemListCellRenderer());
119         scrollPane = new JScrollPane(listUI, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
120
121         /* Utility to able the user to resize the window */
122         JLabel windowResizeCorner = new JLabel("~", JLabel.CENTER);
123         windowResizeCorner.addMouseMotionListener(this);
124         scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, windowResizeCorner);
125
126         /* Completion window */
127         window = new JPanel(new BorderLayout());
128         window.add(scrollPane, BorderLayout.CENTER);
129         window.setSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
130         window.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
131
132         /* Overide Listener */
133         listUI.getInputMap().clear();
134         scrollPane.getInputMap().clear();
135         /* Item selection is available using the keyboard */
136         listUI.addKeyListener(this);
137         /* Item selection is available using the mouse */
138         listUI.addFocusListener(this);
139         listUI.addMouseListener(this);
140
141         getTextComponent().add(window);
142
143         setVisible(false);
144     }
145
146     /**
147      * Make the completion window visible and update its content
148      * @param list list of items matching completion
149      * @param location position of the top left corner of the window
150      * @see com.artenum.rosetta.interfaces.ui.CompletionWindow#show(java.util.List, java.awt.Point)
151      */
152     @Override
153     public abstract void show(List<CompletionItem> list, Point location);
154
155     /**
156      * Get the character string selected by the user as a completion
157      * @return the character string
158      * @see com.artenum.rosetta.interfaces.ui.CompletionWindow#getCompletionResult()
159      */
160     @Override
161     public String getCompletionResult() {
162         return ((CompletionItem) listUI.getSelectedValue()).getReturnValue();
163     }
164
165     /**
166      * Get type of the character string selected by the user as a completion
167      * @return the character string
168      */
169
170     public String getCompletionResultType() {
171         return ((CompletionItem) listUI.getSelectedValue()).getType();
172     }
173
174     /**
175      * Get the visibility status of the completion window
176      * @return true if the window is visible (false else)
177      */
178     public boolean isVisible() {
179         return window.isVisible();
180     }
181
182     /**
183      * Set the visibility status of the completion window
184      * @param status true if the window is visible (false else)
185      */
186     public void setVisible(boolean status) {
187         window.setVisible(status);
188         if (!status) {
189             focusOutComponent.grabFocus();
190         }
191     }
192     /**
193      * List model which allow filter on completion item
194      * In Scilab, filter and sort are done by Scilab and have not to be done here
195      */
196     protected static class CompletionItemListModel extends AbstractListModel {
197         private static final long serialVersionUID = 1L;
198         private List<CompletionItem> data;
199         private String filter;
200
201         /**
202          * Default constructor
203          */
204         public CompletionItemListModel() {
205             data = new ArrayList<CompletionItem>();
206         }
207
208         /**
209          * Get element in the data list
210          * @param index the index of the element to get
211          * @return the corresponding element
212          * @see javax.swing.ListModel#getElementAt(int)
213          */
214         @Override
215         public Object getElementAt(int index) {
216             /* Filtering is done by Scilab */
217             return data.get(index);
218         }
219
220         /**
221          * Get the number of entries in the list
222          * @return the size
223          * @see javax.swing.ListModel#getSize()
224          */
225         @Override
226         public int getSize() {
227             /* Filtering is done by Scilab */
228             return data.size();
229         }
230
231         /**
232          * Set the filter to apply to the dictionnary
233          * @param filterToSet the filter to set
234          */
235         public void setFilter(String filterToSet) {
236
237             if ((filterToSet == null) || ((filterToSet != null) && (filterToSet.length() == 0))) {
238                 filter = null;
239             } else {
240                 filter = filterToSet;
241             }
242             fireContentsChanged(this, 0, getSize());
243         }
244
245         /**
246          * Update the list items
247          * @param list list of item to set to the list
248          * @param list
249          */
250         public void updateData(List<CompletionItem> list) {
251             data.clear();
252             data.addAll(list);
253             Collections.sort(data);
254             setFilter(null);
255         }
256     }
257
258     /**
259      * Add the completed word in the TextComponent
260      * @param position where to append the completed word
261      */
262     public void addCompletedWord(int position) {
263         String currentLine = inputParsingManager.getCommandLine();
264         String lineBeforeCaret = currentLine.substring(0, position);
265         String lineAfterCaret = currentLine.substring(position);
266
267         String stringToAdd = getCompletionResult();
268         String stringToAddType = getCompletionResultType();
269
270         boolean typeStringIsFile = false;
271
272         if (stringToAddType.equals(Messages.gettext("File")) || stringToAddType.equals(Messages.gettext("Directory"))) {
273             typeStringIsFile = true;
274         }
275
276         String newLine = Completion.completelineforjava(lineBeforeCaret, stringToAdd, typeStringIsFile, lineAfterCaret);
277
278         inputParsingManager.reset();
279         inputParsingManager.append(newLine);
280     }
281
282     /**
283      * Add the completed word in the TextComponent
284      * @param stringToAdd the string to add
285      * @param position where to append the completed word
286      */
287     public void addCompletedWord(String stringToAdd, int position) {
288         String currentLine = inputParsingManager.getCommandLine();
289         String lineBeforeCaret = currentLine.substring(0, position);
290         String lineAfterCaret = currentLine.substring(position);
291
292         String newLine = Completion.completelineforjava(lineBeforeCaret, stringToAdd, false, lineAfterCaret);
293
294         inputParsingManager.reset();
295         inputParsingManager.append(newLine);
296     }
297
298     /**
299      * Management of the key typing for the filtering
300      * @param e event
301      */
302     @Override
303     public void keyPressed(KeyEvent e) {
304         if (e.getKeyCode() == KeyEvent.VK_ENTER) {
305             /* The user validates an entry in the list */
306
307             /* Add text to the input command view */
308             if (listUI.getModel().getSize() != 0) {
309                 addCompletedWord(currentCaretPosition);
310             }
311
312             /* Hide the completion window and give the focus to the console */
313             setVisible(false);
314             focusOutComponent.grabFocus();
315         } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
316             /* The user want to exit from completion mode */
317
318             /* Hide the completion window and give the focus to the console */
319             setVisible(false);
320             focusOutComponent.grabFocus();
321         } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
322             /* The user want to select next item */
323
324             if (model.getSize() > 0) {
325                 listUI.setSelectedIndex((listUI.getSelectedIndex()) % model.getSize());
326             }
327         } else if (e.getKeyCode() == KeyEvent.VK_UP) {
328             /* The user want to select previous item */
329
330             if (model.getSize() > 0) {
331                 listUI.setSelectedIndex((model.getSize() + listUI.getSelectedIndex()) % model.getSize());
332             }
333         } else if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
334             /* Delete a character in Scilab input command view */
335             if (inputParsingManager.getPartLevel(inputParsingManager.getCompletionLevel()) != null) {
336                 // Remove a key in input command line
337                 inputParsingManager.backspace();
338
339                 // Display new completion list
340                 Point location = inputParsingManager.getWindowCompletionLocation();
341                 this.show(getCompletionItems(), location);
342                 listUI.setSelectedIndex(0);
343             } else {
344                 /* Hide the completion window and give the focus to the console */
345                 setVisible(false);
346                 focusOutComponent.grabFocus();
347             }
348         } else if (e.getKeyCode() != KeyEvent.VK_TAB) { // IGNORE TAB KEYS BECAUSE ALREADY IN COMPLETION MODE
349             // Add a character in Scilab input command view
350             if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
351                 // Add a key in input command line
352                 inputParsingManager.append(String.valueOf(e.getKeyChar()));
353
354                 // Display new completion list
355                 Point location = inputParsingManager.getWindowCompletionLocation();
356                 this.show(getCompletionItems(), location);
357                 listUI.setSelectedIndex(0);
358             }
359         }
360     }
361
362     /**
363      * What to do when a key is released ?
364      * @param e event
365      */
366     @Override
367     public void keyReleased(KeyEvent e) {
368     }
369
370     /**
371      * What to do when a key is typed ?
372      * @param e event
373      */
374     @Override
375     public void keyTyped(KeyEvent e) {
376     }
377
378     /**
379      * What to do when the completion window gets the focus ?
380      * @param e event
381      */
382     @Override
383     public void focusGained(FocusEvent e) {
384     }
385
386     /**
387      * To support the auto hide when focus is lost
388      * @param e event
389      */
390     @Override
391     public void focusLost(FocusEvent e) {
392         setVisible(false);
393     }
394
395     /**
396      * To support the completion window resize
397      * @param e event
398      */
399     @Override
400     public void mouseDragged(MouseEvent e) {
401         Point origine = window.getLocationOnScreen();
402         Point destination = ((Component) e.getSource()).getLocationOnScreen();
403         destination.x += e.getX();
404         destination.y += e.getY();
405         destination.translate(-origine.x, -origine.y);
406         window.setSize(destination.x, destination.y);
407     }
408
409     /**
410      * What to do when the mouse is moved over the completion window ?
411      * @param e event
412      */
413     @Override
414     public void mouseMoved(MouseEvent e) {
415     }
416
417     /**
418      * What to do when a mouse button is clicked over the completion window ?
419      * @param e event
420      */
421     @Override
422     public void mouseClicked(MouseEvent e) {
423         if (model.getSize() > 0) {
424             /* Select the list item under the mouse */
425             listUI.setSelectedIndex((listUI.getSelectedIndex()) % model.getSize());
426
427             if (e.getClickCount() >= 2) { /* Double click = the user validates the item */
428                 addCompletedWord(currentCaretPosition);
429
430                 /* Hide the completion window and give the focus to the console */
431                 setVisible(false);
432                 focusOutComponent.grabFocus();
433             }
434         }
435     }
436
437     /**
438      * What to do when the mouse enters the completion window ?
439      * @param e event
440      */
441     @Override
442     public void mouseEntered(MouseEvent e) {
443     }
444
445     /**
446      * What to do when the mouse exits over the completion window ?
447      * @param e event
448      */
449     @Override
450     public void mouseExited(MouseEvent e) {
451     }
452
453     /**
454      * What to do when a mouse button mouse is pressed over the completion window ?
455      * @param e event
456      */
457     @Override
458     public void mousePressed(MouseEvent e) {
459     }
460
461     /**
462      * What to do when a mouse button mouse is released over the completion window ?
463      * @param e event
464      */
465     @Override
466     public void mouseReleased(MouseEvent e) {
467         window.validate();
468     }
469 }