001package org.vishia.gral.widget;
002
003import java.io.File;
004import java.io.IOException;
005import java.text.SimpleDateFormat;
006import java.util.Date;
007import java.util.Iterator;
008import java.util.LinkedList;
009import java.util.List;
010import java.util.Map;
011import java.util.TreeMap;
012
013import org.vishia.commander.Fcmd;
014import org.vishia.fileRemote.FileAccessZip;
015import org.vishia.fileRemote.FileCluster;
016import org.vishia.fileRemote.FileMark;
017import org.vishia.fileRemote.FileRemote;
018import org.vishia.fileRemote.FileRemoteCallback;
019import org.vishia.gral.base.GralButton;
020import org.vishia.gral.base.GralMenu;
021import org.vishia.gral.base.GralMng;
022import org.vishia.gral.base.GralPanelContent;
023import org.vishia.gral.base.GralPos;
024import org.vishia.gral.base.GralTable;
025import org.vishia.gral.base.GralTextField;
026import org.vishia.gral.base.GralValueBar;
027import org.vishia.gral.base.GralWidget;
028import org.vishia.gral.base.GralWindow;
029import org.vishia.gral.ifc.GralColor;
030import org.vishia.gral.ifc.GralMngBuild_ifc;
031import org.vishia.gral.ifc.GralMng_ifc;
032import org.vishia.gral.ifc.GralTable_ifc;
033import org.vishia.gral.ifc.GralTextField_ifc;
034import org.vishia.gral.ifc.GralUserAction;
035import org.vishia.gral.ifc.GralTableLine_ifc;
036import org.vishia.gral.ifc.GralWidget_ifc;
037import org.vishia.gral.ifc.GralWindow_ifc;
038import org.vishia.util.Assert;
039import org.vishia.util.Debugutil;
040import org.vishia.util.FileSystem;
041import org.vishia.util.IndexMultiTable;
042import org.vishia.util.KeyCode;
043import org.vishia.util.Removeable;
044import org.vishia.util.MarkMask_ifc;
045import org.vishia.util.SortedTreeWalkerCallback;
046import org.vishia.util.Timeshort;
047
048/**This class is a large widget which contains a list to select files in a directory, 
049 * supports navigation in the directory tree and shows the current path in an extra text field.
050 * Additional 'search in files' is supported.
051 * <br><br>
052 * The type of a file is a {@link FileRemote}. This type inherits from {@link java.io.File}
053 * and is supported for a local file system on PC. It is possible to work with any remote file system. 
054 * <br><br>
055 * The graphical user interface allows some action to do with the right mouse menu:
056 * <ul>
057 * <li>Refresh content
058 * <li>sort files by date latest, newest, by size largest, smallest, by name and by extension.
059 * <li>Search content in files with a dialog window.
060 * </ul>
061 * <b>Callback to users program</b>:<br>
062 * The method {@link #specifyActionOnFileSelected(GralUserAction)} allows any action while the user
063 * select any file. For example a user's program can show the content of the file.
064 * <br><br> 
065 * @author Hartmut Schorrig
066 *
067 */
068public class GralFileSelector extends GralWidget implements Removeable //extends GralWidget
069{
070  
071  /**Version, history and copyright/copyleft.
072   * <ul>
073   * <li>2018-10-28 Hartmut: The Creation and Position is old style yet. Should invoked in the graphic thread. 
074   * TODO: create and position in other (main) thread, createImplWidget_Gthread for Swt incarnation.
075   * <li>2018-10-28 Hartmut: The {@link #favorList} is unused yet, create only if position is given.
076   * <li>2018-10-28 Hartmut: questionWindow is removed yet, TODO: should have better solution. Status line!
077   * <li>2018-10-28 Hartmut: {@link #GralFileSelector(String, int, int[], int[])}: The rows argument is used yet for table zLinesMax. 
078   *   The old last argument size was never used. replaced by the column designation for the {@link #favorList}. 
079   *   If it is null, do not create a {@link #favorList}.
080   * <li>2016-05-02 Hartmut: actionFileSearch writes a result list in the panel of the file window. 
081   *   Yet you can copy the path and insert in the directory text widget to select the file. 
082   *   TODO: Pressing enter to select, store the list etc.
083   * <li>2016-05-02 Hartmut now: derived from GralWidget, it is a large widget 
084   * <li>2016-05-03 Hartmut new: {@link #setVisible(boolean)} for this large widget.
085   * <li>2015-11-19 Hartmut chg: {@link #fillInCurrentDir()} does not refresh if it was refreshed in the last seconds.
086   * <li>2014-12-26 Hartmut chg: {@link #fillIn(FileRemote, boolean)} now with new meaning of boolean: show newly without refresh with the file system.
087   *   This is used in callback routines which does a refresh but does not invoke {@link #showFile(FileRemote)}
088   *   especially called from {@link org.vishia.commander.Fcmd#refreshFilePanel(FileRemote)}. 
089   * <li>2014-01-02 Hartmut chg: does not call 'file.refreshProperties(null);' in {@link #actionSetPath}
090   *   because the property whether it is a directory or not should be known. Prevents a timeout waiting in graphic thread!!! 
091   * <li>2013-09-06 Hartmut chg: Now a new request of fillIn can be executed with aborting the old one.
092   * <li>2013-09-15 Hartmut chg: New implementation of {@link #fillIn(FileRemote, boolean)}
093   *   using the new {@link FileRemote#getChildren(org.vishia.fileRemote.FileRemote.ChildrenEvent).} 
094   * <li>2013-09-14 Hartmut chg: Sets the background to magenta while refreshing.
095   * <li>2013-09-05 Hartmut chg: {@link #getSelectedFiles(boolean, int)} now has that 2 arguments for directory and mark mask.
096   *   Now it is possible and necessary for the application to choice whether directories are gotten too.
097   *   The usage is improved. If some files are marked but not visible, it was able that the user does not know about
098   *   this situation and an unexpected behavior for the user is done. Now only marked files are returned
099   *   if the current file is marked too. The method is correct named 'selected' because the selection is returned,
100   *   either the current file or all marked. 
101   * <li>2013-06-15 Hartmut chg: {@link #fillIn(FileRemote, boolean)} modi of refresh.
102   * <li>2013-05-30 Hartmut new: switch refresh mode on and off
103   * <li>2013-05-20 Hartmut chg: {@link #fillInRefreshed(FileRemote, boolean)} now does not clear
104   *   the table but writes only lines if the data are changed. This method can be called 
105   *   in a higher frequency without disturbing the appearance of the table. Used for cyclically refresh.
106   * <li>2013-05-20 Hartmut new: Context menu for sort etc. It was part of Fcmd, now here.
107   * <li>2013-04-30 Hartmut new: context menu now available. Sort, refresh, deselect
108   * <li>2013-04-30 Hartmut new: {@link #checkRefresh(long)}, chg: Don't change the content in the table
109   *   if the content is identical with the current presentation in table, for refreshing. 
110   * <li>2013-04-30 Hartmut chg: {@link #fillIn(FileRemote, boolean)} now uses the {@link FileRemote#timeRefresh}
111   * <li>2013-04-28 Hartmut new: {@link #actionOnMarkLine} changes the select status of {@link FileRemote#setMarked(int)}
112   * <li>2013-04-12 Hartmut adapt Event, FileRemote: The attributes Event.data1, data2, oData, refData are removed. Any special data should be defined in any derived instance of the event. A common universal data concept may be error-prone  because unspecified types and meanings.
113   *   FileRemote: Dedicated attributes for {@link CallbackCmd#successCode} etc.
114   * <li>2013-03-28 Hartmut chg: {@link #setToPanel(GralMngBuild_ifc, String, int, int[], char)} preserves the panel
115   *   before calling.
116   * <li>2012-11-11 Hartmut bugfix: {@link #fillInRefreshed(File, boolean)}: If all files are complete with file info,
117   *   nevertheless the {@link RefreshTimed#delayedFillin(int) was called because the whole routine was called with false 
118   *   as bCompleteWithFileInfo-argument. Now that is prevented if all files were tested already.
119   * <li>2012-10-12 Hartmut chg {@link WindowFileSelection#openDialog(String, String)} with title.
120   * <li>2012-10-01 Hartmut new now {@link #fillIn(File, boolean)} doesn't get the file properties if it is called with false.
121   *   This makes it faster to show large content of folders on remote devices (in PC-network). If a file is selected
122   *   it replaces its properties. 
123   * <li>2012-09-24 Hartmut new.jar files opened as zip file.
124   * <li>2012-07-30 Hartmut improved using of extra thread on refreshing file properties. Write first 'waiting',
125   *   the other thread writes the content maybe delayed. The user can abort the access if a response is not kept.
126   * <li>2012-07-28 Hartmut improved zipfile access.
127   * <li>2012-07-29 Hartmut improved access to the file system using the new capabilities of FileRemote.
128   *  TODO consequently writing 'wait for response' in table and access to the file in another thread.
129   * <li>2012-07-01 Hartmut new {@link #setActionOnEnterPathNewFile(GralUserAction)}.
130   *   Now this widget is used to select a file to read and save in an application other than the.File.commander.
131   * <li>2012-07-01 Hartmut Refactoring usage of FileRemote: Normally the java.io.File is used.
132   *   The FileRemote is a specialization, which is not used in all cases.
133   * <li>2012-06-17 new Hartmut: Now sorts list with name, size, date, format of size and date in list adjusted:
134   *   Separate timestamp of file for today, last year.
135   * <li>2012-06-09 new Hartmut: {@link GralFileSelector.WindowFileSelection}, not ready yet.
136   * <li>2012-04-16 chg: Capability to sort enhanced.
137   * <li>2012-04-10 chg: Now ctrl-PgUp to select parent dir, like in Norton Commander,
138   *   ctrl-PgDn to entry in dir (parallel to Enter).
139   * <li>2012-03-09 new {@link #kColFilename} etc. 
140   * <li>2012-02-14 corr: Anytime a file was selected as current, it was added in the {@link #indexSelection}.
141   *   Not only if the directory was changed. It is necessary for refresh to select the current file again.
142   * <li>2011-12-11 chg: If the directory is empty, show --empty-- because the table should not be empty.
143   * <li>2011-11-27 new: {@link FileAndName#isWriteable}-property.
144   * <li>2011-11-20 new: Phenomenal basic idea: The files may be located in remote hardware. 
145   *   It means, that a File can't be access here. Therefore the path, name, date, length in the class {@link FileAndName}
146   *   are the data represents on this process on PC. The access to the file is given with remote access. 
147   *   Usage of inner class FileAndName containing path, name, date, length instead a File instance. 
148   * <li>2011-10-02 new: {@link #setActionOnEnterFile(GralUserAction)}. It executes this action if Enter is pressed (or mouse-left- or doubleclick-TODO).
149   * <li>2011-10-01 new: {@link #setOriginDir(File)} and {@link #fillInOriginDir()}.
150   *   The origin dir is the directory of first selection or can be set by user. If the user navigates misty,
151   *   the origin dir helps to find again the start point.
152   * <li>2011-09-28 new: {@link #getCurrentDir()}
153   * <li>2011-08-14 created. Firstly used for the Java commander. But it is a universal file select widget.
154   * </ul>
155   * 
156   * <b>Copyright/Copyleft</b>:<br>
157   * For this source the LGPL Lesser General Public License,
158   * published by the Free Software Foundation is valid.
159   * It means:
160   * <ol>
161   * <li> You can use this source without any restriction for any desired purpose.
162   * <li> You can redistribute copies of this source to everybody.
163   * <li> Every user of this source, also the user of redistribute copies
164   *    with or without payment, must accept this license for further using.
165   * <li> But the LPGL is not appropriate for a whole software product,
166   *    if this source is only a part of them. It means, the user
167   *    must publish this part of source,
168   *    but doesn't need to publish the whole source of the own product.
169   * <li> You can study and modify (improve) this source
170   *    for own using or for redistribution, but you have to license the
171   *    modified sources likewise under this LGPL Lesser General Public License.
172   *    You mustn't delete this Copyright/Copyleft inscription in this source file.
173   * </ol>
174   * If you intent to use this source without publishing its usage, you can get
175   * a second license subscribing a special contract with the author. 
176   * 
177   * @author Hartmut Schorrig = hartmut.schorrig@vishia.de
178   */
179  public static final String sVersion = "2018-10-28";
180  
181  //FileRemoteAccessor localFileAccessor = FileRemoteAccessorLocalFile.getInstance();
182  
183  
184  /**A window for search-in-file dialogue.
185   * It is instantiated calling {@link GralFileSelector#createWindowConfirmSearchGthread(GralMngBuild_ifc)}.
186   * The user can invoke {@link #confirmSearchInFiles(GralFileSelector, Appendable)} to open that window. 
187   * Note: An application may have only one of this window thow more as one GralFileSelector may be exist.
188   * Therefore the window is not instantiated automatically in this class. The user can instantiate it.
189   *
190   */
191  public static class WindowConfirmSearch {
192    
193    GralWindow_ifc windConfirmSearch;
194
195    GralTextField_ifc widgPath, widgMask, widgText;
196    
197    GralValueBar widgProgression;
198    
199    /**Buttons. */
200    GralButton widgEsc, widgSubdirs, widgSearch;
201
202    GralFileSelector fileSelector;
203    
204    Appendable searchOutput;
205    
206    /**Use {@link GralFileSelector#createWindowConfirmSearchGthread(GralMngBuild_ifc)} to create.
207    */
208    protected WindowConfirmSearch(){}
209    
210    
211    /**Shows the window.
212     * @param fileSelector
213     */
214    public void confirmSearchInFiles(GralFileSelector fileSelector, Appendable searchOutput){
215      this.fileSelector = fileSelector;
216      this.searchOutput = searchOutput;
217      this.widgPath.setText(fileSelector.sCurrentDir);
218      windConfirmSearch.setFocus(); //setWindowVisible(true);
219    }
220    
221    
222    /**Action is called if the button search is pressed. */
223    GralUserAction actionFileSearch = new GralUserAction("actionFileSearch"){
224      @Override
225      public boolean exec(int key, GralWidget_ifc widgi, Object... params){ 
226        if(widgi.getCmd().equals("search") && key == KeyCode.mouse1Up){
227          boolean subDirs = widgSubdirs.isOn();
228          String mask = (subDirs ? "**/" : "") + widgMask.getText();
229          String text = widgText.getText();
230          List<File> files = new LinkedList<File>(); 
231          try{
232            FileSystem.addFileToList(fileSelector.currentDir, mask, files);
233            fileSelector.fillIn(files);
234            if(text.length() > 0){
235              FileSystem.searchInFiles(files, text, searchOutput);
236            } else {
237              try{ 
238                for(File file: files){
239                  searchOutput.append("<File=").append(file.getPath()).append(">\n");
240                }
241                searchOutput.append("<done: search in files>\n");
242              }catch(IOException exc){}
243            }
244          } 
245          catch(Exception exc){}
246        }
247        return true;
248      }
249
250    };
251    
252  }
253  
254  protected WindowConfirmSearch windSearch;
255  
256  
257  
258  /**Action to show the file properties in the info line. This action is called anytime if a line
259   * was changed in the file view table. */
260  private final GralUserAction actionOnFileSelection = new GralUserAction(){
261    @Override public boolean userActionGui(int actionCode, GralWidget widgd, Object... params) {
262      if(actionCode == KeyCode.userSelect){
263        @SuppressWarnings("unchecked")
264        GralTableLine_ifc<FileRemote> line = (GralTableLine_ifc<FileRemote>) params[0];
265        if(line != null){
266          FileRemote file = line.getUserData();
267          String sName = line.getCellText(kColFilename);
268          if(file.isTested() && file.exists()) {                       //TODO: if the file is not refreshed, new network access, the GUI hangs a long time.
269            if(sName.equals("..")){
270              currentFile = file; //.getParentFile();
271              currentDir = file;  //the same, the directory of this panel.
272                
273            } else {
274              currentDir = file.getParentFile();
275              currentFile = file;
276              String sDir = file.getParent();
277              indexSelection.put(sDir, file);    //store the last selection.
278            }
279            //System.out.println("GralFileSelector: " + sDir + ":" + sName);
280            if(actionOnFileSelected !=null){
281              actionOnFileSelected.exec(0, selectList.wdgdTable, line, file);
282            }
283            if(line.getCellText(kColDesignation).startsWith("?")){
284              completeLine(line, file, System.currentTimeMillis());
285            }
286          } else {
287            //try refresh the file and show in callback. TODO
288            Debugutil.stop();
289          }
290        }
291      }
292      return true;
293    }
294  };
295  
296  
297  
298  private final MarkMask_ifc actionOnMarkLine = new MarkMask_ifc(){
299
300    @Override public int getMark()
301    {return 0;
302    }
303
304    @Override public int setNonMarked(int mask, Object oData)
305    { assert(oData instanceof FileRemote);
306      FileRemote file = (FileRemote)oData;
307      file.resetMarked(mask);
308      return mask;
309    }
310
311    @Override public int setMarked(int mask, Object oData)
312    { assert(oData instanceof FileRemote);
313      FileRemote file = (FileRemote)oData;
314      file.setMarked(mask);
315      return mask;
316    }
317    
318  };
319
320  
321  /**Implementation of the base widget.
322   */
323  protected class FileSelectList extends GralSelectList<FileRemote>
324  {
325    final GralFileSelector outer;
326    
327    FileSelectList(GralFileSelector outer, String posName, int rows, int[] columns, char size){
328      //super(name, mng);
329      super(posName, rows, columns, size);
330      this.outer = outer;
331      if(columns.length !=4) { throw new IllegalArgumentException("FileSelectList should have 4 columns");}
332      super.setLeftRightKeys(KeyCode.ctrl + KeyCode.pgup, KeyCode.ctrl + KeyCode.pgdn);
333    }
334    
335    
336    
337    @Override public boolean actionOk(Object userData, GralTableLine_ifc line)
338    { boolean done = true;
339      File file = (File)userData;
340      String fileName;
341      //File dir = data.file.getParentFile();
342      //String sDir = dir ==null ? "/" : FileSystem.getCanonicalPath(dir) + "/";
343      String sName = line.getCellText(1);
344      if(sName.equals("..")){
345        actionLeft(userData, line);
346        //String sParent = getParentDir(file);
347        //if(sParent !=null){
348        //  fillIn(sParent); 
349        //}
350      } else if(file !=null && file.isDirectory()){
351        actionRight(userData, line);
352      } else if(file !=null && ((fileName = file.getName()).endsWith(".zip") || fileName.endsWith(".jar"))){
353        actionRightZip(userData, line);
354      } else {
355        if(actionOnEnterFile !=null){
356          actionOnEnterFile.userActionGui(KeyCode.enter, widgdPathDir, file);
357        } else {
358          done = false;
359        }
360      }
361      return done;
362    }
363    
364    
365    private String getParentDir(File data){
366      int zPath = data.getParent().length();
367      int posSep = data.getParent().lastIndexOf('/',zPath-2);
368      if(posSep >=0){
369        String sDirP = data.getParent().substring(0, posSep+1);
370        return sDirP;
371      }
372      else return null;
373    }
374    
375    
376    /**The 'action left' for the FileSelector shows the parent directory.
377     * The {@link GralFileSelector#currentDir} is used to get its parent to show.
378     * @param line the current line. It is unused because userData contains the file.
379     * @param userData The {@link GralTableLine_ifc#getUserData()} from line. 
380     *   It is a java.io.File or a {@link FileRemote}
381     *   which is currently selected. This file is stored as current for the current directory. 
382     *   The parent of the file is the directory which is shown yet.
383     * @see org.vishia.gral.widget.GralSelectList#actionLeft(java.lang.Object, org.vishia.gral.ifc.GralTableLine_ifc)
384     */
385    @Override public void actionLeft(Object userData, GralTableLine_ifc line)
386    {
387      //FileRemote currentFile = (FileRemote)userData;
388      if(currentDir !=null){
389        String sDir = currentDir.getParent();
390        String sName = currentDir.getName();
391        FileRemote parentDir = currentDir.getParentFile();
392        if(parentDir !=null){
393          indexSelection.put(sDir, currentDir); //currentDir.getName());
394          //System.out.println("GralFileSelector: " + sDir + ":" + sName);
395          fillIn(parentDir, false); 
396        }
397      }
398    }
399    
400    
401    @Override public void actionRight(Object userData, GralTableLine_ifc line)
402    {
403      FileRemote currentFile = (FileRemote)userData;
404      //File dir = data.file.getParentFile();
405      //String sDir = dir ==null ? "/" : FileSystem.getCanonicalPath(dir);
406      //String sName = line.getCellText(1);
407      if(currentFile.isDirectory()){
408        //save the last selection of that level
409        //indexSelection.put(currentFile.getParent(), currentFile.getName());
410        fillIn(currentFile, false);
411        //fillIn(data.getParent() + "/" + data.getName());
412      }
413    }
414    
415    
416    public void actionRightZip(Object userData, GralTableLine_ifc line)
417    {
418      FileRemote currentFile = (FileRemote)userData;
419      FileRemote fileZipAsDir = FileAccessZip.examineZipFile(currentFile);
420      //FileZip fileZip = new FileZip(currentFile);
421      fillIn(fileZipAsDir, false);
422    }
423    
424    
425    
426    /* (non-Javadoc)
427     * @see org.vishia.gral.widget.SelectList#actionUserKey(int, java.lang.Object, org.vishia.gral.ifc.GralTableLine_ifc)
428     */
429    @Override public boolean actionUserKey(int keyCode, Object oData, GralTableLine_ifc line)
430    { boolean ret = true;
431      ret = outer.actionUserKey(keyCode, oData, line);
432      return ret;
433    }
434
435
436  } //selectList implementation
437  
438  
439  
440  
441  
442  
443  
444  
445  
446  
447  
448  
449  /**Number of columns of the table. */
450  public static final int zColumns = 4;
451  
452  /**Column which contains the designation of entry.
453   * <ul>
454   * <li>"/" a directory
455   * <li>">" a linked directory (unix systems, symbolic link)
456   * <li>" " space, normal file
457   * <li>"*" a linked file
458   * <li>"#" comparison result: there are differences
459   * <li>"+" comparison result: contains more
460   * <li>"-" comparison result: contains less
461   * 
462   * </ul>
463   */
464  public static final int kColDesignation = 0;
465  
466  /**Column which contains the filename. The column contains either the name of the file
467   * or ".." or "--unknown--".
468   */
469  public static final int kColFilename = 1;
470  
471  /**Column which contains the length of file
472   */
473  public static final int kColLength = 2;
474  
475  /**Column which contains the time stamp
476   */
477  public static final int kColDate = 3;
478  
479  public static final char kSortName = 'n';
480  
481  public static final char kSortNameNonCase = 'N';
482  
483  public static final char kSortExtension = 'x';
484  
485  public static final char kSortExtensionNonCase = 'X';
486  
487  public static final char kSortDateNewest = 'd';
488  
489  public static final char kSortDateOldest = 'o';
490  
491  public static final char kSortSizeLargest = 'l';
492  
493  public static final char kSortSizeSmallest = 's';
494  
495  
496  public static MenuTexts contextMenuTexts = new MenuTexts();
497  
498  
499  private char sortOrder = kSortName, sortOrderLast = '0';
500  
501  /**Determines which time are present in the date/time column.
502   * m a c for last modified, last access, creation
503   */
504  protected char showTime = 'm';
505  
506  
507  /**The implementation of SelectList. */
508  protected FileSelectList selectList;
509  
510  
511  GralColor colorBack, colorBackPending;
512  
513  private final IndexMultiTable<String, GralTableLine_ifc<FileRemote>> idxLines = 
514    new IndexMultiTable<String, GralTableLine_ifc<FileRemote>>(IndexMultiTable.providerString);
515  
516  protected final GralTable<String> favorList;
517  
518  //String name; 
519  //final int rows; 
520  //final int[] columns; 
521  //final char size;
522  
523
524  /**This index stores the last selected file for any directory path which was used.
525   * If the directory path is reused later, the same file will be selected initially.
526   * It helps by navigation through the file tree.
527   * <ul>
528   * <li>The key is the path in canonical form with '/' as separator (in windows too!) 
529   *   but without terminating '/'.
530   * <li>The value is the name of the file in this directory.   
531   * </ul>
532   */
533  private final Map<String, FileRemote> indexSelection = new TreeMap<String, FileRemote>(); 
534  
535  //int lineSelected;
536  
537  /**The time after 1970 when the fillin was invoked and finished at last.
538   * timeFillinFinished=0, then pending.
539   */
540  protected long timeFillinInvoked, timeFilesRefreshed, timeFillinFinished;
541  
542  /**Duration of last fillin. */
543  protected int durationRefresh, durationFillin;
544  
545  protected int refreshCount;
546  
547  boolean donotCheckRefresh = true;
548  
549  /**The widget for showing the path. */
550  protected GralTextField widgdPathDir;
551  
552  protected GralButton widgBtnFavor;
553  
554  String sDatePrefixNewer = "";
555  SimpleDateFormat dateFormatNewer = new SimpleDateFormat("?yy-MM-dd HH:mm:ss"); 
556  
557  String sDatePrefixToday = "";
558  SimpleDateFormat dateFormatToday = new SimpleDateFormat("@ HH:mm:ss"); 
559  
560  String sDatePrefixYear = "";
561  SimpleDateFormat dateFormatYear = new SimpleDateFormat("MMM-dd HH:mm:ss"); 
562  
563  String sDatePrefixOlder = "";
564  SimpleDateFormat dateFormatOlder = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); 
565  
566  //final MainCmd_ifc mainCmd;
567
568  /**The current shown directory. */
569  protected FileRemote currentDir;
570  
571  /**Currently selected file in the table.*/
572  protected FileRemote currentFile;
573  
574  String sCurrentDir;
575  
576  /**Name of the current file.
577   * 
578   */
579  //String sCurrentFile;
580  
581  
582  /**The directory which was used on start. */
583  FileRemote originDir;
584  
585  
586  
587  /**This action will be called any time when the selection of a current file is changed. */
588  protected GralUserAction actionOnFileSelected;
589  
590
591  /**This action will be called on pressing enter or mouse-click on a simple file.
592   */
593  private GralUserAction actionOnEnterFile;
594
595  /**This action will be called on pressing enter or mouse-click on a directory.
596   * Usual the directory can be entered and showed. But the user can do any other action.
597   * If this action returns false, the default behavior: enter the directory will be done.
598   */
599  private GralUserAction actionOnEnterDirectory;
600  
601  
602  /**This action will be called on pressing enter or mouse-click on the path text field
603   * if it contains any text which can't assigned to an existing file.
604   * 
605   */
606  private GralUserAction actionOnEnterPathNewFile;
607  
608  
609  
610  
611  GralUserAction actionSetFileAttribs;
612  
613  
614  //private GralInfoBox questionWindow;
615  
616  
617  private enum ERefresh{ doNothing, refreshAll, refreshChildren}
618
619  //private final EventSource evSrc = new EventSource("GralFileSelector"){};
620  
621  
622  
623  
624  /**Set to true if a fillin is pending. */
625  boolean fillinPending;
626
627  
628  /**Creates
629   * @param posName <code>"@position=name"</code> or <code>"name"</code>, see {@link GralWidget#GralWidget(String, char)} arg1, 
630   * @param rows 
631   * @param columns
632   * @param size
633   */
634  public GralFileSelector(String name, int rows, int[] columns, int[] columnsFavorlist)
635  { //this.name = name; this.rows = rows; this.columns = columns; this.size = size;
636    super(null, name, 'f');
637    favorList = columnsFavorlist !=null ? new GralTable<String>(name, columnsFavorlist) : null;
638    selectList = new FileSelectList(this, name, rows, columns, 'A');
639    colorBack = GralColor.getColor("wh");
640    colorBackPending = GralColor.getColor("pma");
641    //this.mainCmd = mainCmd;
642  }
643  
644  
645  /**Maybe called after construction, should be called before {@link #setToPanel(GralMngBuild_ifc)}
646   * @param name
647   */
648  public void setNameWidget(String name){ 
649    //this.name = name;
650    selectList.wdgdTable.name = name;
651  }
652  
653  
654  public void setDateFormat(String sFormat){
655    dateFormatOlder = new SimpleDateFormat(sFormat);
656  }
657  
658  
659  /**Sets the widgets of this instance to a panel.
660   * The panel and the position in the panel 
661   * should be set before using {@link GralMngBuild_ifc#selectPanel(String)} and 
662   * {@link GralMngBuild_ifc#setPositionInPanel(float, float, float, float, char)}.
663   * The instance has more as one widget, all widgets are set in the area of the given position.
664   * The position area should be a range of at least 3 lines.
665   * @param panelMng The panelManager. 
666   * @param identArgJbat The name of the table widget. The Text-widget for the path gets the name * "-Path".
667   * @param rows Number of rows to show
668   * @param columns Array with column width.
669   * @param size Presentation size. It is a character 'A'..'E', where 'A' is a small size. The size determines
670   *        the font size especially. 
671   */
672  public void createImplWidget_Gthread() //GralMngBuild_ifc panelMng)
673  { GralMng panelMng = GralMng.get();
674    //The macro widget consists of more as one widget. Position the inner widgets:
675    GralPos posAll = panelMng.getPositionInPanel();
676    GralPanelContent panel = posAll.panel;
677    String sPanel = panel.getName();
678    //Text field for path above list
679    panelMng.setPosition(posAll, GralPos.same, GralPos.size + 2.0F, GralPos.same, GralPos.same-6, 1, 'r');
680    widgdPathDir = panelMng.addTextField(null, true, null, null);
681    widgdPathDir.setActionChange(actionSetPath);
682    widgdPathDir.setBackColor(panelMng.getColor("pye"), 0xeeffff);  //color pastel yellow
683    GralMenu menuFolder = widgdPathDir.getContextMenu();
684    menuFolder.addMenuItem("x", "refresh [cR]", actionRefreshFileTable);
685    panelMng.setPosition(GralPos.same, GralPos.same, GralPos.next+0.5f, GralPos.size+5.5f, 1, 'd');
686    widgBtnFavor = new GralButton(null, "favor", actionFavorButton);
687    widgBtnFavor.createImplWidget_Gthread();
688    widgBtnFavor.setVisible(false);
689    //the list
690    //on same position as favor table: the file list.
691    panelMng.setPosition(posAll, GralPos.refer+2, GralPos.same, GralPos.same, GralPos.same, 1, 'd');
692    selectList.createImplWidget_Gthread();
693    selectList.wdgdTable.setVisible(true);
694    selectList.wdgdTable.addContextMenuEntryGthread(1, null, contextMenuTexts.refresh, actionRefreshFileTable);
695    selectList.wdgdTable.addContextMenuEntryGthread(1, null, contextMenuTexts.refreshCyclicOff, actionSwitchoffCheckRefresh);
696    selectList.wdgdTable.addContextMenuEntryGthread(1, null, contextMenuTexts.refreshCyclicOn, actionSwitchonCheckRefresh);
697    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortNameCase, actionSortFilePerNameCase);
698    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortNameNonCase, actionSortFilePerNameNonCase);
699    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortExtCase, actionSortFilePerExtensionCase);
700    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortExtNonCase, actionSortFilePerExtensionNonCase);
701    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortDateNewest, actionSortFilePerTimestamp);
702    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortOldest, actionSortFilePerTimestampOldestFirst);
703    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.showLastModifiedTime, actionShowLastModifiedTime);
704    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.showLastAccessTime, actionShowLastAccessTime);
705    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.showCreationTime, actionShowCreationTime);
706    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sizeLarge, actionSortFilePerLenghLargestFirst);
707    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.sortSizeSmall, actionSortFilesPerLenghSmallestFirst);
708    selectList.wdgdTable.addContextMenuEntryGthread(1, "sort", contextMenuTexts.deselectRecursFiles, actionDeselectDirtree);
709
710    //store this in the GralWidgets to get back from widgets later.
711    widgdPathDir.setContentInfo(this);
712    selectList.wdgdTable.setContentInfo(this);
713    selectList.wdgdTable.specifyActionOnLineSelected(actionOnFileSelection);
714    selectList.wdgdTable.specifyActionOnLineMarked(actionOnMarkLine);
715    
716    panelMng.setPosition(posAll, GralPos.refer+2, GralPos.same, GralPos.same, 0, 1, 'd');
717    //on same position as favor table: the file list.
718    if(favorList !=null) {
719      favorList.setToPanel(panelMng);
720      favorList.insertLine(null, 0, new String[]{"test", "path"}, null);
721      favorList.setVisible(false);
722    }
723    //
724    panelMng.setPosition(5, 0, 10, GralPos.size + 40, 1, 'd');
725    //questionWindow = GralInfoBox.createTextInfoBox(panelMng, "questionInfoBox", "question");  
726    panelMng.selectPanel(sPanel);  //if finished this panel is selected for like entry.
727  }
728  
729  
730  /**Creates the window to confirm search in files. This window can be created only one time
731   * for all file panels, if the application has more as one. On activating the directory
732   * and the file panel to show results should be given. But only one search process can be run
733   * simultaneously.
734   * @return The created window.
735   */
736  public static WindowConfirmSearch createWindowConfirmSearchGthread(GralMngBuild_ifc mng){
737    WindowConfirmSearch wind = new WindowConfirmSearch();
738    mng.selectPanel("primaryWindow");
739    mng.setPosition(-24, 0, -67, 0, 1, 'r'); //right buttom, about half less display width and hight.
740    wind.windConfirmSearch = mng.createWindow("windConfirmSearch", "search in file tree", GralWindow.windConcurrently);
741    mng.setPosition(4, GralPos.size -3.5f, 1, -1, 0, 'd', 0.5f);
742    wind.widgPath = mng.addTextField("path", true, "path", "t");
743    wind.widgMask = mng.addTextField("mask", true, "search name/mask:", "t");
744    wind.widgText = mng.addTextField("containsText", true, "contains text:", "t");
745    
746    mng.setPosition(-5, GralPos.size - 1, 1, -1, 0, 'r',2);
747    wind.widgProgression = mng.addValueBar(null, null);
748    mng.setPosition(-1, GralPos.size - 3, 1, GralPos.size + 8, 0, 'r',2);
749    mng.addButton(null, wind.actionFileSearch, "esc", null, "esc");
750    wind.widgSubdirs = mng.addSwitchButton(null, null, "subdirs", null, "subdirs", "wh", "gn");
751    wind.widgSearch = mng.addButton(null, wind.actionFileSearch, "search", null, "search");
752    wind.widgSearch.setPrimaryWidgetOfPanel();
753    return wind;
754  }
755  
756  /**Sets an action which is called any time when another line is selected.
757   * @param actionOnLineSelected The action, null to switch off this functionality.
758   */
759  public void specifyActionOnFileSelected(GralUserAction actionOnLineSelected){
760    this.actionOnFileSelected = actionOnLineSelected;
761  }
762  
763
764
765  public String getCurrentDirPath(){ return sCurrentDir; }
766  
767  public void setOriginDir(FileRemote dir){ originDir = dir; }
768
769  
770  /**Sets the sort order of entries.
771   * Valid chars for 'sortOrder' are:
772   * <ul>
773   * <li>n: sort by name, case sensitive, A..Z a..z in ASCII order
774   * <li>N: sort by name, non case-sensitive, Aa..Zz, other characters in ASCII order but '_' first.
775   * <li>x: sort by extension, case sensitive. sort by names with equal extension, case sensitive
776   * <li>X: sort by extension, non case sensitive. sort by names with equal extension, non case sensitive
777   * <li>d: sort by timestamp, newest first
778   * <li>o: sort by timestamp, oldest first.
779   * <li>l: sort by size, largest first.
780   * <li>s: sort by size, smallest first.
781   * </ul>
782   * @param sortOrder
783   */
784  public void setSortOrder(char sortOrder){
785    this.sortOrder = sortOrder;
786  }
787  
788  
789  /**Sets the action which is called if any file is entered. It means the Enter-Key is pressed or
790   * a mouse double-click is done on a file.
791   * @param newAction The action to use. The action is invoked with TODO
792   * @return The current assigned action or null.
793   */
794  public GralUserAction setActionOnEnterFile(GralUserAction newAction)
795  { GralUserAction oldAction = actionOnEnterFile;
796    actionOnEnterFile = newAction;
797    return oldAction;
798  }
799  
800  
801  
802  /**This action will be called on pressing enter or mouse-click on a directory.
803   * Usual the directory can be entered and showed. But the user can do any other action.
804   * If this action returns false, the default behavior: enter the directory will be done.
805   */
806  public GralUserAction setActionOnEnterDirectory(GralUserAction newAction)
807  { GralUserAction oldAction = actionOnEnterDirectory;
808    actionOnEnterDirectory = newAction;
809    return oldAction;
810  }
811  
812  
813  /**This action will be called on pressing enter or mouse-click on the path text field
814   * if it contains any text which can't assigned to an existing file.
815   * 
816   */
817  public GralUserAction setActionOnEnterPathNewFile(GralUserAction newAction)
818  { GralUserAction oldAction = actionOnEnterPathNewFile;
819    actionOnEnterPathNewFile = newAction;
820    return oldAction;
821  }
822  
823
824  
825  
826  /**Sets the action which is called if any file is set to the table. 
827   * @param newAction The action to use. The action is invoked with TODO
828   * @return The current assigned action or null.
829   */
830  public GralUserAction setActionSetFileLineAttrib(GralUserAction newAction)
831  { GralUserAction oldAction = actionSetFileAttribs;
832    actionSetFileAttribs = newAction;
833    return oldAction;
834  }
835  
836  
837  /**Fills the content with the first directory or the directory which was set with 
838   * {@link #setOriginDir(File)}.
839   */
840  public void fillInOriginDir()
841  {
842    fillIn(originDir, false);
843  }
844  
845  
846  
847  /**It is the refresh operation.
848   * If {@link #fillinPending} is set yet this operation does nothing. It means it is possible to call in a short cycle
849   * more as one time after another, for example for all files of a directory without calculation effort.
850   */
851  public void fillInCurrentDir(){
852    if(currentDir !=null && !fillinPending) {
853      //assume that a yet tested directory should not refreshed twice because another thread had refreshed already.
854      //yet is 2 seconds.
855      boolean bDonotRefresh = currentDir.isTested(System.currentTimeMillis() - 2000);
856      fillIn(currentDir, bDonotRefresh);
857    }
858  }
859  
860
861
862  private void fillIn(List<File> files) {
863  
864    selectList.wdgdTable.clearTable();
865    idxLines.clear();
866    for(File file1: files ){
867      FileCluster fc = currentDir.itsCluster;
868      FileRemote file = fc.getFile(FileSystem.normalizePath(file1.getAbsolutePath()), null);
869      String name = file.getName();
870      String path = file.getCanonicalPath();
871      GralTableLine_ifc<FileRemote> tline = selectList.wdgdTable.insertLine(path, 0, null, file);
872      tline.setCellText("?", kColDesignation);
873      tline.setCellText(path, kColFilename);
874      tline.setCellText(""+file.length(), kColLength);
875      long timeNow = System.currentTimeMillis();
876      long fileTime;
877      switch(showTime){
878        case 'm': fileTime = file.lastModified(); break;
879        case 'c': fileTime = file.creationTime(); break;
880        case 'a': fileTime = file.lastAccessTime(); break;
881        default: fileTime = -1; //error
882      }
883      
884      long diffTime = timeNow - fileTime;
885      Date timestamp = new Date(fileTime);
886      String sDate;
887      if(diffTime < -10 * 3600000L){
888        sDate = sDatePrefixNewer + dateFormatNewer.format(timestamp);
889      } else if(diffTime < 18*3600000){
890        //files today
891        sDate = sDatePrefixToday + dateFormatToday.format(timestamp);
892      } else if(diffTime < 320 * 24* 3600000L){
893        sDate = sDatePrefixYear + dateFormatYear.format(timestamp);
894      } else {
895        sDate = sDatePrefixOlder + dateFormatOlder.format(timestamp);
896      }
897      tline.setCellText(sDate, kColDate);
898      tline.setBackColor(colorBack, -1);
899      idxLines.put(path, tline);
900    }
901  }
902
903
904
905
906  public void forcefillIn(FileRemote fileIn, boolean bCompleteWithFileInfo) //String path)
907  {
908    fillinPending = false;
909    fillIn(fileIn, bCompleteWithFileInfo);
910  }
911  
912  /**Fills the content with given directory.
913   * If the same directory was refreshed in a short time before, it is not refreshed here.
914   * That is while fast navigation in a tree. 
915   * @param fileIn The directory which's files are shown.
916   * @param bDonotRefrehs false then invoke an extra thread to walk through the file system, 
917   *   see @{@link FileRemote#refreshPropertiesAndChildren(FileRemoteCallback)} and {@link #callbackChildren1}.
918   *   If true then it is presumed that the FileRemote children are refreshed in the last time already.
919   *   The fill the table newly with given content in this thread.
920   */
921  public void fillIn(FileRemote fileIn, boolean bDonotRefrehs) //String path)
922  { long timenow = System.currentTimeMillis();
923    timeFillinInvoked = timenow;
924    final FileRemote dir, file;
925    if(!fileIn.isDirectory()){
926      dir = fileIn.getParentFile(); file = fileIn;
927      String sDir = FileSystem.getCanonicalPath(dir); //with / as separator!
928      String sFile = fileIn.getName();
929      indexSelection.put(sDir, fileIn); //sFile);
930    } else {
931      dir = fileIn; file = null;
932    }
933    if(originDir == null){ 
934      originDir = dir;    //should exist in any case.
935    }
936    
937    fileIn.internalAccess().setRefreshed();
938    boolean bSameDirectory = dir == currentDir;
939    if(!bSameDirectory || !fillinPending){  //new request anytime if other directory, or if it is not pending.
940      fillinPending = true;
941      //selectList.wdgdTable.setBackColor(colorBackPending, -1);  //for all cells.
942      if(!bSameDirectory){
943        currentDir = dir;
944        this.sCurrentDir = dir.getAbsolutePath();  //though it may not exist, store it for refresh (may be exist later).
945      }
946      System.out.println("FcmdFileCard - start fillin; " + sCurrentDir + (bSameDirectory ? "; same" : "; new"));
947      final ERefresh eRefresh;
948      if(bSameDirectory){
949        //it is a refresh.
950        if(true || (timenow - dir.timeChildren) > 4000){
951          eRefresh = ERefresh.refreshAll; //needs to refresh
952        } else {
953          eRefresh = ERefresh.doNothing;  //do nothing, it is actual
954        }
955      } else {
956        //other directory
957        currentFile = null;
958              //the directory is unknown yet.
959          //GralTableLine_ifc<FileRemote> tline = selectList.wdgdTable.insertLine(null, 0, null, null);
960          //tline.setCellText("--waiting--", kColFilename);
961          eRefresh = ERefresh.refreshChildren;  //do nothing, it is shown and refreshed in execFillIn
962      }
963      if(!bSameDirectory || sortOrder != sortOrderLast){
964        selectList.wdgdTable.clearTable();
965        idxLines.clear();
966        if(dir.getParentFile() !=null){
967          GralTableLine_ifc<FileRemote> tline = selectList.wdgdTable.insertLine("..", 0, null, dir);
968          tline.setCellText("<", kColDesignation);
969          tline.setCellText("..", kColFilename);
970          tline.setCellText("", kColLength);
971          tline.setCellText("", kColDate);
972          tline.setBackColor(colorBack, -1);
973          idxLines.put("..", tline);
974        }
975        //Build the table lines newly.
976      } else {
977        for(GralTable<?>.TableLineData line: selectList.wdgdTable.iterLines()){
978          //if(!line.getCellText(kColFilename).equals("..")){
979            line.setBackColor(colorBackPending, -1);
980          //}
981        }
982      }
983      widgdPathDir.setText(sCurrentDir, -1);
984      sortOrderLast = sortOrder;
985      ////
986      if(bDonotRefrehs) {
987        //do not refresh, show given files.
988        Map<String, FileRemote> files = dir.children();
989        if(files !=null) {
990          for(Map.Entry<String,FileRemote> entry: files.entrySet()) {
991            FileRemote file1 = entry.getValue();
992            showFile(file1);
993          }
994        }
995        finishShowFileTable();   //removed lines with not existing files, 
996        ////
997      } else {
998        //refresh it in an extra thread therefore show all lines with colorBackPending. 
999        //Remove lines which remains the colorBackPending after refreshing.
1000        dir.refreshPropertiesAndChildren(callbackChildren1, false);
1001      }
1002    }
1003  }
1004  
1005  
1006  
1007  /**This routine is invoked in callback of {@link #callbackChildren1} for {@link #fillIn(FileRemote, boolean)} in the refresh thread.
1008   * @param file1
1009   */
1010  void showFile(FileRemote file1)
1011  {
1012    String key = buildKey(file1, true, null);
1013    boolean[] found = new boolean[1];
1014    GralTableLine_ifc<FileRemote> tline = idxLines.search(key, false, found);
1015    
1016    if(!found[0]){ //no such line with this file
1017      String name = file1.getName();  //use the file name as key in the table for the table line.
1018      if(tline ==null){
1019        //on empty table, first line.
1020        tline = selectList.wdgdTable.insertLine(name, 0, null, file1);
1021      }else {
1022        //insert after found line.
1023        tline = tline.addNextLine(name, null, file1);
1024      }
1025      tline.setCellText(file1.getName(), kColFilename);
1026      idxLines.add(key, tline);
1027    }
1028    completeLine(tline, file1, System.currentTimeMillis());
1029    tline.setBackColor(colorBack, -1); //set for the whole line.
1030  }
1031  
1032  
1033  
1034  /**Finishes a newly showed file table.
1035   * Removes all lines which have the {@link #colorBackPending} yet, they are not refreshed because that files don't exist furthermore.
1036   * Gets the {@link #currentFile()} of this table from the {@link #indexSelection} if the {@link #currentFile} is null,
1037   * sets the current line and repaint the table.
1038   */
1039  void finishShowFileTable()
1040  {
1041    System.out.println("FcmdFileCard - finish fillin; " + sCurrentDir);
1042    Iterator<Map.Entry<String, GralTableLine_ifc<FileRemote>>> iter = idxLines.entrySet().iterator();
1043    while(iter.hasNext()){
1044      Map.Entry<String, GralTableLine_ifc<FileRemote>> entry = iter.next();
1045      GralTableLine_ifc<FileRemote> tline = entry.getValue();
1046      if(tline.getKey().equals("..")){
1047        tline.setBackColor(colorBack, -1); //set for the whole line.
1048      } else if(tline.getBackColor(-1) == colorBackPending){
1049        selectList.wdgdTable.deleteLine(tline);  //it is a non existing one yet.
1050        iter.remove();
1051      }
1052    }
1053    selectList.wdgdTable.setBackColor(colorBack, GralTable_ifc.kEmptyArea);
1054    if(currentFile == null){
1055      currentFile = indexSelection.get(sCurrentDir);
1056    }
1057    GralTableLine_ifc<FileRemote> tline;
1058    if(currentFile !=null){
1059      String key = buildKey(currentFile, true, null);
1060      tline = idxLines.search(key); //maybe line before
1061    } else {
1062      tline = null;  //first line is selected
1063    }
1064    if(tline !=null){
1065      selectList.wdgdTable.setCurrentLine(tline, -3, 1);  
1066      currentFile = tline.getUserData();  //adjust the file if the currentFile was not found exactly.
1067    }
1068    selectList.wdgdTable.repaint(100, 200);
1069    fillinPending = false;
1070    
1071  }
1072  
1073  
1074
1075  
1076  @SuppressWarnings("boxing")
1077  private void completeLine(GralTableLine_ifc<FileRemote> tline, FileRemote file, long timeNow){
1078    final String sDesign, sDir;
1079    int mark = file.mark ==null ? 0 : file.mark.getMark();
1080    if(file.isSymbolicLink()){ 
1081      sDir =  file.isDirectory() ? ">" : "s"; 
1082    }
1083    else if(file.isDirectory()){
1084      sDir = "/";
1085    } else {
1086      sDir = "";
1087    }
1088    if(mark != 0){
1089      if((mark & FileMark.cmpFileDifferences ) !=0){ sDesign = "#"; }
1090      else if((mark & FileMark.cmpMissingFiles ) !=0){ sDesign = "*"; }  //directory contains more files
1091      else if((mark & FileMark.cmpAlone ) !=0){ sDesign = "+"; }
1092      else if((mark & FileMark.mCmpFile) !=0){
1093        switch(mark & FileMark.mCmpFile){
1094          case FileMark.cmpContentEqual: sDesign = " ";break;
1095          case FileMark.cmpContentNotEqual: sDesign = "#";break;
1096          default: sDesign = " ";
1097        }
1098      } else {
1099        sDesign = " ";
1100      }
1101    }
1102    else { sDesign = " ";}
1103    tline.setCellText(sDir + sDesign, kColDesignation);
1104    long fileTime;
1105    switch(showTime){
1106      case 'm': fileTime = file.lastModified(); break;
1107      case 'c': fileTime = file.creationTime(); break;
1108      case 'a': fileTime = file.lastAccessTime(); break;
1109      default: fileTime = -1; //error
1110    }
1111    
1112    long diffTime = timeNow - fileTime;
1113    Date timestamp = new Date(fileTime);
1114    String sDate;
1115    if(diffTime < -10 * 3600000L){
1116      sDate = sDatePrefixNewer + dateFormatNewer.format(timestamp);
1117    } else if(diffTime < 18*3600000){
1118      //files today
1119      sDate = sDatePrefixToday + dateFormatToday.format(timestamp);
1120    } else if(diffTime < 320 * 24* 3600000L){
1121      sDate = sDatePrefixYear + dateFormatYear.format(timestamp);
1122    } else {
1123      sDate = sDatePrefixOlder + dateFormatOlder.format(timestamp);
1124    }
1125    tline.setCellText(sDate, kColDate);
1126    //line[kColDate] = sDate;
1127    //
1128    String sLength;
1129    long fileLength = file.length();
1130    if(fileLength < 1024){
1131      sLength = "" + fileLength;
1132    } else if(fileLength < 10000){
1133      sLength = String.format("%1.1f k", fileLength / 1024.0f);
1134    } else if(fileLength < 1000000){
1135      sLength = String.format("%3.0f k", fileLength / 1024.0f);
1136    } else if(fileLength < 10000000){
1137      sLength = String.format("%1.1f M", fileLength / (1024 * 1024.0f));
1138    } else if(fileLength < 1000000000){
1139      sLength = String.format("%3.0f M", fileLength / (1024 * 1024.0f));
1140    } else if(fileLength < 10000000000L){
1141      sLength = String.format("%1.1f G", fileLength / (1024 * 1024.0f));
1142    } else {
1143      sLength = String.format("%2.0f G", fileLength / (1024 * 1024.0f));
1144    }
1145    tline.setCellText(sLength, kColLength);
1146    tline.setContentIdent(fileTime);
1147    //line[kColLength] = sLength;
1148    
1149  }
1150  
1151  
1152  ////
1153  protected String buildKey(FileRemote file, boolean bAllCompleteWithFileInfo, String[] retSortName){
1154    String sort, sortName;
1155    if(!file.isTested()){
1156      bAllCompleteWithFileInfo = false;
1157    }
1158    switch(sortOrder){
1159    case kSortName: {
1160      String sName = file.getName();
1161      if(file.isDirectory()){ sName += "/"; }
1162      sortName = sort = (file.isDirectory()? "D" : "F") + sName;
1163    } break;
1164    case kSortNameNonCase: {
1165      String sName = file.getName().toLowerCase();
1166      if(file.isDirectory()){ sName += "/"; }
1167      sortName = sort = (file.isDirectory()? "D" : "F") + sName;
1168    } break;
1169    case kSortExtension: {
1170      String sName = file.getName();
1171      int posDot = sName.lastIndexOf('.');
1172      String sExt = sName.substring(posDot+1);
1173      if(file.isDirectory()){ sName += "/"; }
1174      sortName = sort = (file.isDirectory()? "D" : "F") + sExt + sName;
1175    } break;
1176    case kSortExtensionNonCase: {
1177      String sName = file.getName().toLowerCase();
1178      int posDot = sName.lastIndexOf('.');
1179      String sExt = sName.substring(posDot+1);
1180      if(file.isDirectory()){ sName += "/"; }
1181      sortName = sort = (file.isDirectory()? "D" : "F") + sExt + sName;
1182    } break;
1183    case kSortDateNewest: {
1184      String sName = file.getName().toLowerCase();
1185      if(bAllCompleteWithFileInfo){
1186        long nDate = -file.lastModified();
1187        String sDate = String.format("%016X", new Long(nDate));
1188        sort = (file.isDirectory()? "D" : "F") + sDate + sName;
1189      } else { sort = ""; }
1190      sortName = (file.isDirectory()? "D" : "F") + sName;
1191    } break;
1192    case kSortDateOldest: {
1193      String sName = file.getName().toLowerCase();
1194      if(bAllCompleteWithFileInfo){
1195        long nDate = file.lastModified();
1196        String sDate = String.format("%016X", new Long(nDate));
1197        sort = (file.isDirectory()? "D" : "F") + sDate + sName;
1198      } else { sort = ""; }
1199      sortName = (file.isDirectory()? "D" : "F") + sName;
1200    } break;
1201    case kSortSizeLargest: {
1202      String sName = file.getName().toLowerCase();
1203      if(bAllCompleteWithFileInfo){
1204        long nSize = 0x7fffffffffffffffL - file.length();
1205        String sSize = String.format("%016d", new Long(nSize));
1206        sort = (file.isDirectory()? "D" : "F") + sSize + sName;
1207      } else { sort = ""; }
1208      sortName = (file.isDirectory()? "D" : "F") + sName;
1209    } break;
1210    case kSortSizeSmallest: {
1211      String sName = file.getName().toLowerCase();
1212      if(bAllCompleteWithFileInfo){
1213        long nSize = file.length();
1214        String sSize = String.format("%016d", new Long(nSize));
1215        sort = (file.isDirectory()? "D" : "F") + sSize + sName;
1216      } else { sort = ""; }
1217      sortName = (file.isDirectory()? "D" : "F") + sName;
1218    } break;
1219    default: { sortName = sort = file.getName(); }
1220    }
1221    if(retSortName !=null){ retSortName[0] = sortName; }
1222    return sort;
1223  }
1224  
1225  
1226  
1227  
1228  public void checkRefresh(long since){
1229    if(currentDir !=null 
1230      && (  !donotCheckRefresh && !currentDir.isTested(since - 5000)
1231         || currentDir.shouldRefresh()
1232       )   ){
1233      fillIn(currentDir, false);
1234    }
1235  }
1236  
1237  
1238  //public FileRemote getCurrentDir(){ return currentDir; }
1239
1240  
1241  /**Gets the selected file from this panel.
1242   * @return null if no line is selected, for example if the panel isn't used yet.
1243   */
1244  public File XXXgetSelectedFile()
1245  {
1246    if(selectList.wdgdTable == null){
1247      stop();
1248      return null;
1249    }
1250    GralTableLine_ifc<FileRemote> line = selectList.wdgdTable.getCurrentLine();
1251    if(line !=null){
1252      File data = line.getUserData();
1253      return data;
1254    } else {
1255      return null;
1256    }
1257  }
1258  
1259
1260
1261  
1262  /**Gets the selected file from this panel.
1263   * If the current file of this panel is marked, then all other marked files and directories
1264   * of this panel are returned too. If the current file is not marked, then only this current file
1265   * is returned as selected file.
1266   * <br><br>
1267   * Strategy changed since 2013-09-05: Before, the marked files are returned. But it is possible
1268   * that some files are marked outside of the visible area, and the user does not know that there are
1269   * marked files.
1270   * In this case an unexpected behavior from the user's view occurs. 
1271   * If the user selects a marked file, then one should be sure that there are other marked files 
1272   * in non visible areas too.
1273   * 
1274   * @param bAlsoDirs false then returns never a directory. If the current selected file is a directory
1275   *   then return null. 
1276   * @return null if no line is selected, for example if the panel isn't used yet. 
1277   *   If the current file is non-marked, then returns a list of only 1 element, that current file.
1278   *   If the current file is marked, return all marked files. 
1279   */
1280  public List<FileRemote> getSelectedFiles(boolean bAlsoDirs, int mask)
1281  { if(selectList.wdgdTable == null){
1282      stop();
1283      return null;
1284    } else if(currentFile == null){
1285      return null;
1286    } else if(currentFile.isMarked(0x1)){
1287      List<FileRemote> list = new LinkedList<FileRemote>();
1288      for(GralTableLine_ifc<FileRemote> line: selectList.wdgdTable.getMarkedLines(mask)){
1289        FileRemote file = line.getUserData();
1290        if(bAlsoDirs || !file.isDirectory()){
1291          list.add(file);
1292        }
1293      }
1294      return list;
1295    } else if(bAlsoDirs || !currentFile.isDirectory()) {
1296      List<FileRemote> list = new LinkedList<FileRemote>();
1297      list.add(currentFile);
1298      return list;
1299    } else { //no mark file selected, current file is a directory.
1300      return null;
1301    }
1302  }
1303  
1304  
1305  /**Selects the file with the given name in the table
1306   * @param name name of file like it is shown in the table (given as key).
1307   * @return true if found and selected.
1308   */
1309  public boolean selectFile(String name){
1310    return selectList.wdgdTable.setCurrentLine(name);
1311    
1312  }
1313  
1314  
1315  /**Gets the current selected file. 
1316   * Note: If the .. is selected, the current file is the parent directory.
1317   * If any directory is selected, this is the currentFile(). Check with {@link java.io.File#isDirectory()}.
1318   */
1319  public FileRemote currentFile(){ return currentFile; }
1320  
1321  /**Gets the directory which is currently shown.
1322   * Note: If the .. is selected, the current directory is the directory where the .. is located,
1323   * whereby the {@link #currentFile()} is the parent. Elsewhere this method returns the parent of
1324   * {@link #currentFile()}.
1325   */
1326  public FileRemote currentDir(){ return currentDir; }
1327  
1328  /**same as {@link #currentDir()}. */
1329  public FileRemote getCurrentDir(){ return currentDir; }
1330  
1331  
1332  
1333  /**Sets the focus of the associated table widget.
1334   * @return true if focused.
1335   */
1336  public void setFocus(){ selectList.wdgdTable.setFocus(); }
1337  
1338  
1339  @Override public boolean setVisible(boolean visible)
1340  {
1341    selectList.wdgdTable.setVisible(visible);
1342    widgdPathDir.setVisible(visible);
1343    widgBtnFavor.setVisible(visible);
1344    return bVisibleState; 
1345  }
1346  
1347  void stop(){}
1348
1349  @Override public boolean remove(){ 
1350    selectList.remove();
1351    widgdPathDir.remove();
1352    indexSelection.clear();
1353    currentDir = null;
1354    return true;
1355  }
1356  
1357  
1358  
1359  /**This method is called on any user key or mouse event while operating in the file table.
1360   * It should be overwritten by a derived class. This routine is empty.
1361   * @param key code or mouse code, one of constants from {@link KeyCode}.
1362   * @param userDataOfLine The user data stored in the line of table.
1363   * @param line The table line.
1364   * @return true if is was relevant for the key.
1365   */
1366  public boolean actionUserKey(int keyCode, Object userDataOfLine, GralTableLine_ifc<FileRemote> line)
1367  { 
1368    return false;
1369  }
1370
1371  
1372  
1373  
1374  public FileRemoteCallback callbackChildren1 = new FileRemoteCallback()
1375  {
1376
1377    @Override public void start(FileRemote startDir){}
1378
1379    @Override public Result offerParentNode(FileRemote file) {
1380      return Result.cont; 
1381    }
1382
1383    @Override public Result finishedParentNode(FileRemote file, FileRemoteCallback.Counters cnt) {
1384      //don't call showFile(file) because it is the parent which should be shown.
1385      return Result.cont; 
1386    }
1387
1388    @Override public Result offerLeafNode(FileRemote file, Object info)
1389    { showFile(file);  //invoked also for dirs because depth=1
1390      return Result.cont;
1391    }
1392
1393    @Override public void finished(FileRemote startDir, SortedTreeWalkerCallback.Counters cnt)
1394    {
1395      finishShowFileTable();
1396    }
1397
1398    @Override public boolean shouldAborted()
1399    { return false;
1400    }
1401  };
1402  
1403  
1404  
1405  
1406  
1407  
1408  
1409  /**Action on [Enter]-key on the path text field.
1410   * <ul>
1411   * <li>If the text represents an existing directory, it is used as current.
1412   * <li>If the text represents an existing file, the parent directory is used as current
1413   *   and the file is stored as current in this directory, see {@link #indexSelection}.
1414   * <li>If the path is absolute (starting with '/' or '\\' maybe with leading drive letter for windows
1415   *   then it is used absolute. If the path is not absolute, it is used starting from the current one.
1416   * <li>If the path contains a name only, it is a file. You can    
1417   * <li>If the text represents a file which is not existing, but its directory path is existing,
1418   *   a quest is posted whether the file should be created. On [OK] either the file will be
1419   *   created as new file or its path is returned.    
1420   * </ul>
1421   */
1422  GralUserAction actionSetPath = new GralUserAction(){
1423    @Override
1424    public boolean userActionGui(int key, GralWidget widg, Object... params)
1425    {
1426      if(key == KeyCode.enter){
1427        String sPath = widg.getValue();
1428        int posWildcard = sPath.indexOf('*');
1429        if(posWildcard >=0){
1430          
1431        } else {
1432          FileRemote file = originDir.itsCluster.getDir(sPath);
1433          //file.refreshProperties(null);
1434          if(file.isDirectory()){
1435            fillIn(file, false);
1436          } else if(file.isFile()){
1437            FileRemote dir = file.getParentFile();
1438            String sDir = FileSystem.getCanonicalPath(dir);
1439            String sFile = file.getName();
1440            indexSelection.put(sDir, file);
1441            fillIn(dir, false);
1442          } else {
1443            File parent = file.getParentFile();
1444            if(parent !=null && parent.exists()){
1445              if(actionOnEnterPathNewFile !=null){
1446                actionOnEnterPathNewFile.userActionGui(KeyCode.enter, widgdPathDir, file);
1447              } else {
1448                String question = "Do you want to create file\n"
1449                  +file.getName()
1450                  + "\n  in directory\n"
1451                  + parent.getPath();
1452//                questionWindow.setText(question);
1453//                questionWindow.setActionOk(confirmCreate);
1454//                questionWindow.setFocus(); //setWindowVisible(true);
1455              }
1456            } else {
1457//              questionWindow.setText("unknown path");
1458//              questionWindow.setActionOk(null);
1459//              questionWindow.setFocus(); //setWindowVisible(true);
1460            }
1461          }
1462        }
1463        //widg.getMng().widgetHelper.showContextMenu(widg);
1464      }
1465      
1466      stop();
1467      return false;
1468    }
1469  };
1470  
1471
1472  public void setSortOrderFiles(char order){
1473    setSortOrder(order);
1474    fillInCurrentDir();
1475  }
1476  
1477
1478  
1479  GralUserAction actionSortFilePerNameCase = new GralUserAction("actionSortFilePerNameCase")
1480  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1481      setSortOrderFiles(GralFileSelector.kSortName);
1482      return true;
1483  } };
1484
1485
1486  GralUserAction actionSortFilePerNameNonCase = new GralUserAction("actionSortFilePerNameNonCase")
1487  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1488      setSortOrderFiles(GralFileSelector.kSortNameNonCase);
1489      return true;
1490  } };
1491
1492
1493  GralUserAction actionSortFilePerExtensionCase = new GralUserAction("actionSortFilePerExtensionCase")
1494  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1495      setSortOrderFiles(GralFileSelector.kSortExtension);
1496      return true;
1497  } };
1498
1499  GralUserAction actionSortFilePerExtensionNonCase = new GralUserAction("actionSortFilePerExtensionNonCase")
1500  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1501      setSortOrderFiles(GralFileSelector.kSortExtensionNonCase);
1502      return true;
1503  } };
1504
1505  GralUserAction actionSortFilePerTimestamp = new GralUserAction("actionSortFilePerTimestamp")
1506  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1507      setSortOrderFiles(GralFileSelector.kSortDateNewest);
1508      return true;
1509  } };
1510
1511  GralUserAction actionSortFilePerTimestampOldestFirst = new GralUserAction("actionSortFilePerTimestampOldestFirst")
1512  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1513      setSortOrderFiles(GralFileSelector.kSortDateOldest);
1514      return true;
1515  } };
1516
1517  GralUserAction actionShowLastModifiedTime = new GralUserAction("actionSortFilePerTimestamp")
1518  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1519      showTime = 'm';
1520      return true;
1521  } };
1522
1523  GralUserAction actionShowLastAccessTime = new GralUserAction("actionSortFilePerTimestamp")
1524  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1525      showTime = 'a';
1526      return true;
1527  } };
1528
1529  GralUserAction actionShowCreationTime = new GralUserAction("actionSortFilePerTimestamp")
1530  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1531      showTime = 'c';
1532      return true;
1533  } };
1534
1535  GralUserAction actionSortFilePerLenghLargestFirst = new GralUserAction("actionSortFilePerLenghLargestFirst")
1536  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1537      setSortOrderFiles(GralFileSelector.kSortSizeLargest);
1538      return true;
1539  } };
1540
1541  GralUserAction actionSortFilesPerLenghSmallestFirst = new GralUserAction("actionSortFilesPerLenghSmallestFirst")
1542  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1543      setSortOrderFiles(GralFileSelector.kSortSizeSmallest);
1544      return true;
1545  } };
1546
1547
1548  GralUserAction actionDeselectDirtree = new GralUserAction("actionDeselectDirtree")
1549  { @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params) { 
1550    //if(fileCard !=null){
1551    if(currentFile !=null){
1552      Thread run1 = new Thread("actionDeselectDirtree") {
1553        @Override public void run() {
1554          currentFile.resetMarkedRecurs(0xffffffff, null);
1555          currentFile.setDirShouldRefresh();
1556      } };
1557      run1.start();
1558    }
1559    return true;
1560  } };
1561
1562
1563
1564  
1565  
1566  /**Sets the origin dir of the last focused file table.
1567   * <br>
1568   * Implementation note: The last focused file tab is searched using {@link Fcmd#getLastSelectedFileCards()}.
1569   */
1570  GralUserAction actionRefreshFileTable = new GralUserAction(){
1571    @Override public boolean userActionGui(int key, GralWidget widgd, Object... params){ 
1572      if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
1573        fillInCurrentDir();
1574        return true;
1575      } else return false;
1576    }
1577  };
1578  
1579  /**Sets the check refresh mode to off.
1580   * <br>
1581   */
1582  GralUserAction actionSwitchoffCheckRefresh = new GralUserAction("actionSwitchoffCheckRefresh"){
1583    @Override public boolean userActionGui(int key, GralWidget widgd, Object... params){ 
1584      if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
1585        donotCheckRefresh = true;
1586        return true;
1587      } else return false;
1588    }
1589  };
1590  
1591  /**Sets the check refresh mode to on.
1592   * <br>
1593   */
1594  GralUserAction actionSwitchonCheckRefresh = new GralUserAction("actionSwitchoffCheckRefresh"){
1595    @Override public boolean userActionGui(int key, GralWidget widgd, Object... params){ 
1596      if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
1597        donotCheckRefresh = false;
1598        return true;
1599      } else return false;
1600    }
1601  };
1602  
1603  GralUserAction confirmCreate = new GralUserAction()
1604  {
1605    @Override public boolean userActionGui(int key, GralWidget widgd, Object... params){ 
1606      return true;
1607    }
1608  };
1609  
1610  
1611  
1612  
1613  GralUserAction actionFavorButton = new GralUserAction("actionFavorButton"){
1614    @Override public boolean exec(int key, GralWidget_ifc widgd, Object... params){ 
1615      if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
1616        if(selectList.wdgdTable.isVisible()){
1617          selectList.wdgdTable.setVisible(false);
1618          favorList.setVisible(true);
1619        } else {
1620          selectList.wdgdTable.setVisible(true);
1621          favorList.setVisible(false);
1622        }
1623        return true;
1624      } else return false;
1625    }
1626  };
1627  
1628
1629  
1630  /**This class is instantiated static and contains English menu texts. The user can change it
1631   * touching the public static instance {@link GralFileSelector#contextMenuTexts}
1632   * before calling {@link GralFileSelector#setToPanel(GralMngBuild_ifc, String, int, int[], char)}. 
1633   */
1634  public static class MenuTexts{
1635    public String refresh = "&Refresh [F5]";
1636    public String sortNameCase = "&Sort/&Name case sensit";
1637    public String sortNameNonCase = "&Sort/&Name non-case";
1638    public String sortExtCase = "&Sort/e&Xt case sensit";
1639    public String sortExtNonCase = "&Sort/e&Xt non-case";
1640    public String sortOldest = "&Sort/date &Oldest";
1641    public String sortDateNewest = "&Sort/&Date newest";
1642    public String sizeLarge = "&Sort/size &Largest";
1643    public String sortSizeSmall = "&Sort/size &Smallest";
1644    public String deselectRecursFiles = "actionDeselectDirtree";
1645    public String refreshCyclicOff = "Cyclic refresh/o&ff";
1646    public String refreshCyclicOn = "Cyclic refresh/&on";
1647    public String showLastAccessTime = "show date last &Access";
1648    public String showLastModifiedTime = "show date last &Modified";
1649    public String showCreationTime = "show date &Creation";
1650  }
1651  
1652  
1653}