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