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}