File Browser: add an entry in the main menu
[scilab.git] / scilab / modules / ui_data / src / java / org / scilab / modules / ui_data / filebrowser / SwingScilabTreeTable.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011 - DIGITEO - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.ui_data.filebrowser;
14
15 import java.awt.Color;
16 import java.awt.Component;
17 import java.awt.Dimension;
18 import java.awt.Graphics;
19 import java.awt.Insets;
20 import java.awt.Point;
21 import java.awt.Rectangle;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.awt.event.KeyAdapter;
25 import java.awt.event.KeyEvent;
26 import java.awt.event.MouseAdapter;
27 import java.awt.event.MouseEvent;
28 import java.io.File;
29 import java.lang.reflect.Method;
30 import java.text.DateFormat;
31 import java.util.Date;
32 import java.util.Enumeration;
33 import java.util.regex.Pattern;
34
35 import javax.swing.AbstractCellEditor;
36 import javax.swing.ActionMap;
37 import javax.swing.InputMap;
38 import javax.swing.JLabel;
39 import javax.swing.JMenuItem;
40 import javax.swing.JPopupMenu;
41 import javax.swing.JScrollPane;
42 import javax.swing.JTable;
43 import javax.swing.JTree;
44 import javax.swing.KeyStroke;
45 import javax.swing.SwingUtilities;
46 import javax.swing.border.AbstractBorder;
47 import javax.swing.border.Border;
48 import javax.swing.plaf.basic.BasicTreeUI;
49 import javax.swing.table.DefaultTableCellRenderer;
50 import javax.swing.table.TableCellEditor;
51 import javax.swing.table.TableCellRenderer;
52 import javax.swing.table.TableColumn;
53 import javax.swing.tree.TreePath;
54
55 import org.scilab.modules.gui.events.callback.CallBack;
56 import org.scilab.modules.gui.pushbutton.PushButton;
57 import org.scilab.modules.ui_data.filebrowser.actions.ChangeCWDAction;
58 import org.scilab.modules.ui_data.filebrowser.actions.EditFileWithDefaultAppAction;
59 import org.scilab.modules.ui_data.filebrowser.actions.ExecuteFileInConsoleAction;
60 import org.scilab.modules.ui_data.filebrowser.actions.ExecuteFileInXcosAction;
61 import org.scilab.modules.ui_data.filebrowser.actions.LoadFileAsGraphAction;
62 import org.scilab.modules.ui_data.filebrowser.actions.LoadFileInScilabAction;
63 import org.scilab.modules.ui_data.filebrowser.actions.OpenFileInSciNotesAction;
64 import org.scilab.modules.ui_data.filebrowser.actions.OpenFileWithDefaultAppAction;
65 import org.scilab.modules.ui_data.filebrowser.actions.ValidateAction;
66 import org.scilab.modules.ui_data.utils.UiDataMessages;
67
68 /**
69  * The tree table model abstract implementation
70  * @author Calixte DENIZET
71  */
72 public class SwingScilabTreeTable extends JTable {
73
74     private static final Insets INSETS = new Insets(0, 2, 0, 0);
75     private static final DateFormat DATEFORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
76
77     private static final Border BORDER = new AbstractBorder() {
78             public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
79                 g.setColor(Color.LIGHT_GRAY);
80                 g.drawLine(x, y, x, y + height);
81             }
82
83             public Insets getBorderInsets(Component c) {
84                 return INSETS;
85             }
86
87             public Insets getBorderInsets(Component c, Insets insets) {
88                 return INSETS;
89             }
90         };
91
92     private Method isLocationInExpandControl;
93
94     protected ScilabTreeTableCellRenderer tree;
95     protected ScilabFileSelectorComboBox combobox;
96     protected ScilabFileBrowserHistory history;
97
98     /**
99      * Default Constructor
100      * @param treeTableModel the tree table model
101      * @param combobox the combox used to set the path
102      */
103     public SwingScilabTreeTable(ScilabTreeTableModel treeTableModel, ScilabFileSelectorComboBox combobox) {
104         super();
105         this.combobox = combobox;
106         combobox.setTreeTable(this);
107         history = new ScilabFileBrowserHistory(this);
108         tree = new ScilabTreeTableCellRenderer(this, treeTableModel);
109         super.setModel(new ScilabTreeTableModelAdapter(treeTableModel, tree));
110
111         // Install the tree editor renderer and editor.
112         setDefaultRenderer(ScilabTreeTableModel.class, tree);
113         setDefaultRenderer(Date.class, new DefaultTableCellRenderer() {
114                 {
115                     setHorizontalTextPosition(DefaultTableCellRenderer.LEFT);
116                 }
117
118             public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focus, int row, int col) {
119                 JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, selected, focus, row, col);
120                 label.setText(DATEFORMAT.format((Date) value));
121                 if (col == 1) {
122                     label.setBorder(BORDER);
123                 }
124                 return label;
125             }
126         });
127         setDefaultRenderer(ScilabFileBrowserModel.FileSize.class, new DefaultTableCellRenderer() {
128                 {
129                     setHorizontalTextPosition(DefaultTableCellRenderer.LEFT);
130                 }
131
132             public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focus, int row, int col) {
133                 Component c = super.getTableCellRendererComponent(table, value, selected, focus, row, col);
134                 if (col == 1) {
135                     JLabel jl = (JLabel) c;
136                     jl.setBorder(BORDER);
137                 }
138                 return c;
139             }
140         });
141         setDefaultRenderer(String.class, new DefaultTableCellRenderer() {
142                 {
143                     setHorizontalTextPosition(DefaultTableCellRenderer.LEFT);
144                 }
145
146             public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focus, int row, int col) {
147                 Component c = super.getTableCellRendererComponent(table, value, selected, focus, row, col);
148                 if (col == 1) {
149                     JLabel jl = (JLabel) c;
150                     jl.setBorder(BORDER);
151                 }
152                 return c;
153             }
154         });
155
156         setShowGrid(false);
157         setFillsViewportHeight(true);
158         setIntercellSpacing(new Dimension(0, 0));
159         setRowSorter(new FileBrowserRowSorter(tree, this));
160         setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN);
161
162         try {
163             isLocationInExpandControl = BasicTreeUI.class.getDeclaredMethod("isLocationInExpandControl", new Class[]{TreePath.class, int.class, int.class});
164             isLocationInExpandControl.setAccessible(true);
165         } catch (NoSuchMethodException e) { }
166
167         addMouseListener(new MouseAdapter() {
168                 public void mousePressed(MouseEvent e) {
169                     Point p = e.getPoint();
170                     int col = columnAtPoint(p);
171                     if (getColumnClass(col) == ScilabTreeTableModel.class && SwingUtilities.isLeftMouseButton(e)) {
172                         MouseEvent me = e;
173                         if (isLocationInExpandControl != null) {
174                             try {
175                                 int row = rowAtPoint(p);
176                                 TreePath path = tree.getPathForRow(row);
177                                 boolean isOnExpander = ((Boolean) isLocationInExpandControl.invoke(tree.getUI(), path, e.getX(), e.getY())).booleanValue();
178                                 Rectangle r = tree.getRowBounds(row);
179                                 if (!isOnExpander && !r.contains(p)) {
180                                     me = new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(), e.getModifiers(), r.x, r.y, e.getClickCount(), e.isPopupTrigger());
181                                 }
182                             } catch (Exception ex) { }
183                         }
184                         tree.dispatchEvent(me);
185                     }
186                 }
187             });
188
189         addKeyListener(new KeyAdapter() {
190                 public void keyTyped(KeyEvent e) {
191                     char c = e.getKeyChar();
192                     if (Character.isLetter(c)) {
193                         int step = 1;
194                         if (Character.isUpperCase(c)) {
195                             step = -1;
196                         }
197                         c = Character.toLowerCase(c);
198                         int[] rows = getSelectedRows();
199                         int count = getRowCount();
200                         int start = 0;
201                         if (rows != null && rows.length != 0) {
202                             start = modulo(rows[0] + step, count);
203                         }
204                         for (int i = start; i != start - step; i = modulo(i + step, count)) {
205                             char first = ((FileNode) tree.getPathForRow(i).getLastPathComponent()).toString().charAt(0);
206                             first = Character.toLowerCase(first);
207                             if (first == c) {
208                                 scrollRectToVisible(tree.getRowBounds(i));
209                                 setRowSelectionInterval(i, i);
210                                 break;
211                             }
212                         }
213                     }
214                 }
215             });
216
217
218         initActions();
219         setComponentPopupMenu(createPopup());
220     }
221
222     /**
223      * @return the Next button used in history
224      */
225     public PushButton getNextButton() {
226         return history.getNextButton();
227     }
228
229     /**
230      * @return the Previous button used in history
231      */
232     public PushButton getPreviousButton() {
233         return history.getPreviousButton();
234     }
235
236     /**
237      * @return the combobox used to set the path
238      */
239     public ScilabFileSelectorComboBox getComboBox() {
240         return combobox;
241     }
242
243     /**
244      * Get the selected rows as file path
245      * @return the paths
246      */
247     public String[] getSelectedPaths() {
248         int[] rows = getSelectedRows();
249         String[] paths = new String[rows.length];
250         for (int i = 0; i < rows.length; i++) {
251             TreePath path = tree.getPathForRow(rows[i]);
252             FileNode fn = (FileNode) path.getLastPathComponent();
253             paths[i] = fn.getFile().getAbsolutePath();
254         }
255
256         return paths;
257     }
258
259     /**
260      * Get the selected rows as file
261      * @return the paths
262      */
263     public File[] getSelectedFiles() {
264         int[] rows = getSelectedRows();
265         File[] files = new File[rows.length];
266         for (int i = 0; i < rows.length; i++) {
267             TreePath path = tree.getPathForRow(rows[i]);
268             FileNode fn = (FileNode) path.getLastPathComponent();
269             files[i] = fn.getFile();
270         }
271
272         return files;
273     }
274
275     /**
276      * {@inheritDoc}
277      */
278     public int getRowHeight(int row) {
279         return getRowHeight();
280     }
281
282     /**
283      * {@inheritDoc}
284      */
285     public boolean isOpaque() {
286         return false;
287     }
288
289     /**
290      * Set the base directory
291      * @param baseDir the base directory
292      */
293     public void setBaseDir(String baseDir) {
294         setBaseDir(baseDir, true);
295     }
296
297     /**
298      * Set the base directory
299      * @param baseDir the base directory
300      * @param addInHistory if true the dir is add in the history
301      */
302     public void setBaseDir(String baseDir, boolean addInHistory) {
303         ScilabFileBrowserModel model = (ScilabFileBrowserModel) tree.getModel();
304         combobox.setBaseDir(baseDir);
305         if (model != null) {
306             File f = new File(baseDir);
307             if (!baseDir.equals(model.getBaseDir()) && f.exists() && f.isDirectory() && f.canRead()) {
308                 tree.setModel(null);
309                 if (addInHistory) {
310                     history.addPathInHistory(baseDir);
311                 }
312                 model.setBaseDir(baseDir, this);
313             }
314         }
315     }
316
317     /**
318      * Set the file filter to use in table
319      * @param pat the pattern to use
320      */
321     public void setFilter(Pattern pat) {
322         ScilabFileBrowserModel model = (ScilabFileBrowserModel) tree.getModel();
323         TreePath rootPath = new TreePath(model.getRoot());
324         Enumeration<TreePath> en = tree.getExpandedDescendants(rootPath);
325         tree.setModel(null);
326         model.setFilter(pat);
327         reload(model);
328         if (en != null) {
329             while (en.hasMoreElements()) {
330                 tree.expandPath(en.nextElement());
331             }
332         }
333     }
334
335     /**
336      * Reload the table
337      */
338     public void reload(ScilabFileBrowserModel model) {
339         tree.setModel(model);
340         tree.setRowHeight(getRowHeight());
341         tree.setLargeModel(true);
342         TreePath path = new TreePath(model.getRoot());
343         tree.collapsePath(path);
344         ((JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, this)).getVerticalScrollBar().setValue(0);
345         tree.expandPath(path);
346         if (getRowCount() >= 1) {
347             repaint(tree.getRowBounds(1));
348         }
349         editingRow = 0;
350     }
351
352     /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
353      * paint the editor. The UI currently uses different techniques to
354      * paint the renderers and editors and overriding setBounds() below
355      * is not the right thing to do for an editor. Returning -1 for the
356      * editing row in this case, ensures the editor is never painted.
357      */
358     public int getEditingRow() {
359         if (getColumnClass(editingColumn) == ScilabTreeTableModel.class) {
360             return -1;
361         } else {
362             return editingRow;
363         }
364     }
365
366     /**
367      * Init the actions
368      */
369     private void initActions() {
370         final ActionMap actions = getActionMap();
371         actions.put("scinotes", new OpenFileInSciNotesAction(this));
372         actions.put("xcos", new ExecuteFileInXcosAction(this));
373         actions.put("console", new ExecuteFileInConsoleAction(this));
374         actions.put("load", new LoadFileInScilabAction(this));
375         actions.put("graph", new LoadFileAsGraphAction(this));
376         actions.put("cwd", new ChangeCWDAction(this));
377         if (EditFileWithDefaultAppAction.isSupported()) {
378             actions.put("edit", new EditFileWithDefaultAppAction(this));
379         }
380         if (OpenFileWithDefaultAppAction.isSupported()) {
381             actions.put("open", new OpenFileWithDefaultAppAction(this));
382         }
383         actions.put("validate", new ValidateAction(this));
384         actions.put("validateorexpand", new CallBack(null) {
385                 public void callBack() {
386                     int[] rows = getSelectedRows();
387                     if (rows != null && rows.length != 0) {
388                         TreePath path = tree.getPathForRow(rows[0]);
389                         FileNode fn = (FileNode) path.getLastPathComponent();
390                         if (fn.isLeaf()) {
391                             ((CallBack) actions.get("validate")).callBack();
392                         } else {
393                             if (tree.isExpanded(path)) {
394                                 tree.collapsePath(path);
395                             } else {
396                                 tree.expandPath(path);
397                             }
398                             setRowSelectionInterval(rows[0], rows[0]);
399                         }
400                     }
401                 }
402             });
403
404         combobox.setAction((CallBack) actions.get("cwd"));
405         InputMap map = getInputMap();
406         map.put(KeyStroke.getKeyStroke("ENTER"), "validateorexpand");
407     }
408
409     /**
410      * Create the popup menu
411      */
412     private JPopupMenu createPopup() {
413         ActionMap actions = getActionMap();
414         JPopupMenu popup = new JPopupMenu();
415         JMenuItem item = new JMenuItem(UiDataMessages.OPENINSCINOTES);
416         item.addActionListener(actions.get("scinotes"));
417         popup.add(item);
418
419         item = new JMenuItem(UiDataMessages.EXECINCONSOLE);
420         item.addActionListener(actions.get("console"));
421         popup.add(item);
422
423         item = new JMenuItem(UiDataMessages.OPENINXCOS);
424         item.addActionListener(actions.get("xcos"));
425         popup.add(item);
426
427         item = new JMenuItem(UiDataMessages.LOADINSCILAB);
428         item.addActionListener(actions.get("load"));
429         popup.add(item);
430
431         if (actions.get("edit") != null || actions.get("open") != null) {
432             popup.addSeparator();
433         }
434
435         if (actions.get("edit") != null) {
436             item = new JMenuItem(UiDataMessages.EDITWITHDEFAULT);
437             item.addActionListener(actions.get("edit"));
438             popup.add(item);
439         }
440
441         if (actions.get("open") != null) {
442             item = new JMenuItem(UiDataMessages.OPENWITHDEFAULT);
443             item.addActionListener(actions.get("open"));
444             popup.add(item);
445         }
446
447         popup.pack();
448
449         return popup;
450     }
451
452     /**
453      * A modulo for negative numbers
454      * @param n an int
455      * @param p an other int
456      * @return n modulo p
457      */
458     private static final int modulo(int n, int p) {
459         if (n >= 0) {
460             return n % p;
461         }
462         return p - (-n % p);
463     }
464
465 }