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