001package org.vishia.commander;
002
003import java.io.BufferedReader;
004import java.io.ByteArrayInputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.io.InputStreamReader;
008import java.io.UnsupportedEncodingException;
009import java.nio.ByteBuffer;
010import java.nio.channels.ReadableByteChannel;
011import java.nio.channels.WritableByteChannel;
012import java.nio.charset.Charset;
013
014import org.vishia.fileRemote.FileRemote;
015import org.vishia.gral.base.GralButton;
016import org.vishia.gral.base.GralPos;
017import org.vishia.gral.base.GralTextBox;
018import org.vishia.gral.base.GralTextField;
019import org.vishia.gral.base.GralWidget;
020import org.vishia.gral.base.GralWindow;
021import org.vishia.gral.ifc.GralColor;
022import org.vishia.gral.ifc.GralTextFieldUser_ifc;
023import org.vishia.gral.ifc.GralUserAction;
024import org.vishia.gral.ifc.GralWidget_ifc;
025import org.vishia.gral.ifc.GralWindow_ifc;
026import org.vishia.util.KeyCode;
027import org.vishia.util.StringFormatter;
028
029/**All functionality of view (F3-Key) and edit a file. 
030 * @author Hartmut Schorrig
031 * */
032public class FcmdView
033{
034  protected final Fcmd main;
035
036  /**The window of this functionallity. */
037  private GralWindow_ifc windView;
038
039  /**The widget to show content. */
040  private GralTextBox widgContent;
041  
042  private GralTextField widgFindText, widgShowInfo;
043  
044  private GralButton btnFind, btnWholeword, btnCase, btnQuickview;
045  
046  boolean bVisible;
047  
048  boolean bEditable;
049  
050  int nrQuickview;
051  
052  private GralTextBox widgQuickView;
053  
054  FileRemote file;
055  
056  /**A buffer to get bytes from the file using the java.nio.Channel mechanism. 
057   * The channel mechanism is proper to work with remote file access especially.
058   * Note: The ByteBuffer may be a part of the channel mechanism itself, 
059   * because it is placed JVM-internally,
060   * for example for socket communication. TODO check and change it.
061   * The size of the byteBuffer is set to less than 1 UDP-telegram payload
062   * to support any communication.
063   */
064  private final ByteBuffer tmpReadTransmissionBuffer = ByteBuffer.allocate(1200);
065
066  /**Number of read bytes. */
067  private int zContent;
068
069
070  /**The gotten bytes from bytebuffer. This buffer is set to the size of the file, if the file
071   * is less than a maximal size. */
072  private final byte[] uContent = new byte[10000000];
073  
074  
075  private Charset encodingContent;
076  
077  //private byte[] outBuffer = new byte[20000];
078  
079  /**The current choiced view format.
080   * <ul>
081   * <li>a: us-ascii-text
082   * <li>w: iso (Windows)-text
083   * <li>u: UTF16-text
084   * <li>1: Hexa byte wise
085   * <li>2: Hexa 16-bit-words
086   * <li>4: Hexa 32-bit-words
087   * <li>F: contains, float values, shows it
088   * </ul>  
089   */
090  private char format = 't';
091  
092  private int cursorPos;
093  
094  private static Charset ascii7 = Charset.forName("US-ASCII");
095  
096  private static Charset utf8 = Charset.forName("UTF8");
097  
098  private static Charset iso8859_1 = Charset.forName("ISO-8859-1");
099  
100  private static byte[] endl_0a = { 0x0a };
101  
102  private static byte[] endl_0d0a = { 0x0d, 0x0a };
103  
104  
105  /**Instance to prepare the text especially for hex view. */
106  private final StringFormatter formatterHex = new StringFormatter(120);
107  
108  public FcmdView(Fcmd main)
109  { this.main = main;
110  }
111  
112  
113  /**Builds the content of the confirm-delete window. The window is created static. It is shown
114   * whenever it is used.  */
115  void buildWindowView()
116  {
117    main._gralMng.selectPanel("primaryWindow");
118    main._gralMng.setPosition(10, 0, 10, 0, 1, 'r'); //right buttom, about half less display width and hight.
119    int windProps = GralWindow.windConcurrently | GralWindow.windHasMenu | GralWindow.windResizeable
120                  | GralWindow.windOnTop;
121    GralWindow wind =  main._gralMng.createWindow("windView", "view - The.file.Commander", windProps);
122    wind.addMenuBarItemGThread(null, "&File/&Save", actionSave);
123    wind.addMenuBarItemGThread(null, "&File/Save-as &UTF8-Unix-lf", actionSaveTextAsUTF8unix);
124    wind.addMenuBarItemGThread(null, "&File/Save-as &Windows (ISO-8859-1)", actionSaveTextAsWindows);
125    wind.addMenuBarItemGThread(null, "&File/Save-as &ISO-8859-1-Unix-lf", actionSaveTextAsISO8859_1_unix);
126    wind.addMenuBarItemGThread("view-Search", "&View/&Hex-Byte", actionSetHexView);
127    wind.addMenuBarItemGThread("view-Search", "&View/text-&Windows", actionSetTextViewISO8859_1);
128    wind.addMenuBarItemGThread("view-Search", "&View/text-&UTF", actionSetTextViewUTF8);
129    wind.addMenuBarItemGThread("view-Search", "&View/text-&ASCII-7", actionSetTextViewISO8859_1);
130    wind.addMenuBarItemGThread("view-Search", "&View/text-&Encoding", actionSetTextViewISO8859_1);
131    wind.addMenuBarItemGThread("view-Search", "&Edit/&Search", actionSetTextViewISO8859_1);
132    wind.addMenuBarItemGThread("view-Search", "&Edit/set &Writeable", actionSetEditable);
133    main._gralMng.setPosition(0.5f, 2.5f, 1, 20, 0, 'r');
134    widgFindText = main._gralMng.addTextField(null, true, null, null);
135    main._gralMng.setPosition(0.5f, 2.5f, 22, GralPos.size + 10, 0, 'r', 1);
136    btnFind = main._gralMng.addButton(null, actionFind, null, null, "Search (ctrl-F)");
137    btnWholeword = main._gralMng.addSwitchButton(null, "wholeWord - no", "wholeWord- yes", GralColor.getColor("wh"), GralColor.getColor("gn"));
138    btnCase = main._gralMng.addSwitchButton(null, "case - no", "case - yes", GralColor.getColor("wh"), GralColor.getColor("gn"));
139    btnQuickview = main._gralMng.addSwitchButton("qview", "qview", "qview", GralColor.getColor("wh"), GralColor.getColor("gn"));
140    widgShowInfo = main._gralMng.addTextField(null,false, null, null);
141    main._gralMng.setPosition(3, 0, 0, 0, 1, 'r');
142    widgContent = main._gralMng.addTextBox("view-content", false, null, '.');
143    widgContent.setUser(userKeys);
144    widgContent.setTextStyle(GralColor.getColor("bk"), main._gralMng.propertiesGui.getTextFont(2.0f, 'm', 'n'));
145    windView = wind; 
146    windView.specifyActionOnCloseWindow(actionOnSetInvisible);
147    windView.setWindowVisible(false);
148    //windView1.
149  }
150
151  
152  
153  
154  /**Reads the current (last selected) file in the binary content buffer, 
155   * detects its encoding, shows it.
156   * The file is held in a binary content buffer independent of its content type.
157   * The hexa view shows the bytes in any case. If the file will be shown as text,
158   * either the encoding can be self - detect, or the encoding can be selected by the user.
159   * Opens the view window and fills its content.
160   * @param src The path which is selected as source. It may be a directory or a file.
161   */
162  void view(FileRemote XXXsrc)
163  { //String sSrc, sTrash;
164    file = main.currentFile();
165    if(file !=null){
166      long len = file.length();
167      //if(len > 1000000){ len = 1000000; } //nor more than 1MByte, 
168      //uContent = new byte[(int)len + 10000];
169      zContent = 0;
170      ReadableByteChannel reader = file.openRead(0);
171      try{
172        if(reader == null){
173          widgContent.setText("File is not able to read:\n");
174          widgContent.append(file.getAbsolutePath());
175        } else {
176          int nrofBytesRead;
177          do{
178            tmpReadTransmissionBuffer.clear();
179            nrofBytesRead = reader.read(tmpReadTransmissionBuffer);
180            if(nrofBytesRead >0){
181              tmpReadTransmissionBuffer.rewind();
182              if(zContent + nrofBytesRead > uContent.length){
183                nrofBytesRead = uContent.length - zContent;
184              }
185              if(nrofBytesRead > 0){
186                tmpReadTransmissionBuffer.get(uContent, zContent, nrofBytesRead);
187                zContent += nrofBytesRead;
188              }
189            }
190          } while(nrofBytesRead >0); //Stop if no bytes read or uContent is full.
191          reader.close();
192          //byteBuffer.rewind();
193          presentContent();
194        }
195      } catch(IOException exc){
196        
197      }
198    }
199    if(!bVisible){
200      windView.setWindowVisible(true);
201      windView.setFocus();
202      bVisible = true;
203    }
204  }
205  
206  
207  
208  /**This routine will be called whenever a file is selected newly, it checks quickview.
209   * 
210   * 
211   */
212  public void quickView(){
213    if(btnQuickview.isOn()){
214      nrQuickview +=1;
215      widgShowInfo.setText("" + nrQuickview);
216      view(null);
217    }
218  }
219  
220
221  
222  void detectEncoding(){
223    encodingContent = iso8859_1;
224    format = 'w';
225    for(int ii =0; ii<zContent; ++ii){
226      byte cc = uContent[ii];
227      if(cc < 0x20 && cc >=0 && "\r\n\t".indexOf(cc) <0){
228        //non-text character
229        encodingContent = null;
230        return;
231      }
232    }
233  }
234  
235  
236  
237  void presentContent() throws IOException
238  {
239    widgContent.setEditable(false);
240    bEditable = false;
241    detectEncoding();
242    if(encodingContent!=null){
243      presentContentText(encodingContent);
244    } else {
245      widgContent.setText("file ...\n");
246      cursorPos = 0;
247      format = 'h';
248      presentContentHex();
249    }
250  }
251  
252  
253  
254  void presentContentHex() throws IOException
255  {
256    int pos0;
257    try{
258      String sFile = "file: " + file.getAbsolutePath() + "\n";
259      widgContent.setText(sFile);
260      pos0 = sFile.length();
261      //byteBuffer.get(buffer);
262      for(int ii = 0; ii < 16 && ii < uContent.length /16; ++ii){
263        formatterHex.reset();
264        formatterHex.addHex(ii, 4).add(": ");
265        formatterHex.addHexLine(uContent, 16*ii, 16, StringFormatter.k1);
266        formatterHex.add("  ").addStringLine(uContent, 16*ii, 16, ascii7.name());
267        widgContent.append(formatterHex.getContent()).append('\n');
268      }
269      int posCursor = pos0 + (6 + 3*16 + 2 + 17)* (cursorPos/16) + 6 + 3 * (cursorPos % 16);
270      widgContent.setCursorPos(posCursor);
271    } catch(Exception exc){
272      widgContent.append(exc.getMessage());
273    }
274  }
275  
276  
277  
278  void presentContentText(Charset charset) throws UnsupportedEncodingException
279  {
280    encodingContent = charset;
281    String content = new String(uContent, 0, zContent, charset);
282    widgContent.setText(content, 0);
283    widgContent.setCursorPos(cursorPos);
284  }
285  
286  
287  void syncContentFromWidg(){
288    switch(format){
289    case 'h':{
290      int posInWidg = widgContent.getCursorPos();
291      
292      //cursorPos;
293    }break;
294    case 'w': syncTextFromWidg(iso8859_1); break;
295    case 'u': syncTextFromWidg(utf8); break;
296    default:  
297    }
298  }
299  
300  
301  void syncTextFromWidg(Charset charset){
302    int posInWidg = widgContent.getCursorPos();
303    cursorPos = posInWidg;
304    ////
305    if(bEditable) {
306    //if(widgContent.isChanged(true)){    //TODO isChanged does not work yet. It may better.
307      String sContent = widgContent.getText();
308      int zContentnew = sContent.length();
309      int pos = -1;
310      
311      byte[] bytes = sContent.getBytes(charset);  //TODO portions of substring with size about 4096 ...16000
312      for(byte bb: bytes){
313        uContent[++pos] = bb;
314      }
315      int end = zContent -1;  //to set 0-bytes
316      zContent = ++pos;       //new size
317      while(pos < end){
318        uContent[++pos] = 0;  //remove rest, let it clean.
319      }
320    }
321    
322  }
323    
324  
325  
326  void saveTextAs(Charset encoding, byte[] lineEnd){
327    try{
328      //read the content in the given encoding:
329      InputStream inpBytes = new ByteArrayInputStream(uContent, 0, zContent);
330      InputStreamReader inpText = new InputStreamReader(inpBytes, encodingContent);
331      BufferedReader inpLines = new BufferedReader(inpText);
332      FileRemote filedst = main.currentFile();
333      WritableByteChannel outchn =filedst.openWrite(0);
334      ByteBuffer outBuffer = ByteBuffer.allocate(1200);
335      //Writer out = new FileWriter();
336      String sLine;
337      do{
338        sLine = inpLines.readLine();
339        if(sLine !=null){
340          byte[] bytes = sLine.getBytes(encoding);
341          if(outBuffer.remaining() < bytes.length+1){
342            int zOut = outBuffer.position();
343            outBuffer.limit(zOut);
344            outBuffer.rewind();
345            outchn.write(outBuffer);
346            outBuffer.limit(outBuffer.capacity());
347            outBuffer.clear();  
348          }
349          int posBytes = 0;
350          int zOutBuffer;
351          while( (zOutBuffer = outBuffer.remaining()) < (bytes.length - posBytes +1)){
352            outBuffer.put(bytes, posBytes, zOutBuffer);
353            outBuffer.limit(zOutBuffer);
354            outBuffer.rewind();
355            outchn.write(outBuffer);
356            outBuffer.clear();
357            posBytes += zOutBuffer; 
358          }
359          outBuffer.put(bytes, posBytes, bytes.length - posBytes)
360                   .put(lineEnd);
361          //outText.append(sLine).append('\n');
362        }
363      } while(sLine !=null);
364      int zOut = outBuffer.position();
365      outBuffer.limit(zOut);
366      outBuffer.rewind();
367      outchn.write(outBuffer);
368      outBuffer.clear(); 
369      outchn.close();
370      
371    } catch(Exception exc){
372      main._gralMng.writeLog(0, exc);
373    }
374    
375  }
376  
377  
378  
379  void openQuickView(FileRemote src){
380    if(widgQuickView == null){
381      //creates an grid panel and select its in gralMng:
382      main.favorPathSelector.panelRight.tabbedPanelFileCards.addGridPanel("qview", "qview",1,1,10,10);
383      main._gralMng.setPosition(1, -1, 0, 0, 1, 'd');
384      //adds a textBox in that grid panel.
385      widgQuickView = main._gralMng.addTextBox("qview-content", false, null, '.');
386      widgQuickView.setText("quick view");
387      widgQuickView.setFocus();
388    }
389    else {
390      closeQuickView();
391    }
392  }
393  
394  
395  
396  void closeQuickView(){
397    main.favorPathSelector.panelRight.tabbedPanelFileCards.removePanel("qview");
398    widgQuickView.remove();
399    widgQuickView = null;
400  }
401  
402  
403
404  
405  
406  /**Action for Key crl-Q for quick view command. Its like Norton Commander.
407   */
408  GralUserAction actionQuickView = new GralUserAction("quick view")
409  {
410    @Override public boolean exec(int key, GralWidget_ifc widgi, Object... params)
411    { if(KeyCode.isControlFunctionMouseUpOrMenu(key)){
412        openQuickView(null);
413        return true;
414      } else return false; 
415      // /
416    }
417  };
418
419
420  
421
422  
423  
424  
425  /**Action for Key F3 for view command. Its like Norton Commander.
426   */
427  GralUserAction actionFind = new GralUserAction("actionFind")
428  {
429    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
430    { if(KeyCode.isControlFunctionMouseUpOrMenu(key)){
431        String text = widgContent.getText();
432        String find = widgFindText.getText();
433        int pos0 = widgContent.getCursorPos();
434        int pos1 = text.indexOf(find, pos0+1);
435        if(pos1 > 0){
436          widgContent.setCursorPos(pos1);
437        }
438        return true;
439      } else return false; 
440      // /
441    }
442  };
443
444
445  
446  
447  /**Action for Key F3 for view command. Its like Norton Commander.
448   */
449  GralUserAction actionOpenView = new GralUserAction("actionOpenView")
450  {
451    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
452    { if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
453      btnQuickview.setState(GralButton.State.On);
454      view(null);
455        return true;
456      } else return false; 
457      // /
458    }
459  };
460
461
462  /**Action for Key F3 for view command. Its like Norton Commander.
463   */
464  GralUserAction actionSetTextViewUTF8 = new GralUserAction("actionSetTextViewUTF8")
465  {
466    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
467    { if(key != KeyCode.mouse1Down){  //supress both mouse up and down reaction
468      try{ 
469        format = '?';
470        presentContentText(utf8);
471        format = 'u';
472      } catch(UnsupportedEncodingException exc){
473        System.err.println("FcmdView.actionSetTextViewUTF8 - UnsupportedEncodingException; unexpected");
474      }
475      return true;
476      } else return false; 
477      // /
478    }
479  };
480
481
482  /**Action for Key F3 for view command. Its like Norton Commander.
483   */
484  GralUserAction actionSetHexView = new GralUserAction("actionSetHexView")
485  {
486    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
487    {  if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
488       try{ 
489        syncContentFromWidg();
490        format = '?';
491        presentContentHex();
492        format = 'h';
493      } catch(Exception exc){
494        System.err.println("FcmdView.actionSetHexView - Exception; unexpected");
495      }
496      return true;
497      } else return false; 
498      // /
499    }
500  };
501
502
503  /**Action for Key F3 for view command. Its like Norton Commander.
504   */
505  GralUserAction actionSetTextViewISO8859_1 = new GralUserAction("actionSetTextViewISO8859_1")
506  {
507    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
508    { if(key != KeyCode.mouse1Down){  //supress both mouse up and down reaction
509      try{ 
510        format = '?';
511        presentContentText(iso8859_1);
512        format = 'w';
513      } catch(UnsupportedEncodingException exc){
514        System.err.println("FcmdView.actionSetTextViewISO8859_1 - UnsupportedEncodingException; unexpected");
515      }
516      return true;
517      } else return false; 
518      // /
519    }
520  };
521
522
523  /**Action for Key F3 for view command. Its like Norton Commander.
524   */
525  GralUserAction actionSetEditable = new GralUserAction("actionSetEditable")
526  {
527    @Override public boolean userActionGui(int key, GralWidget infos, Object... params)
528    { if(KeyCode.isControlFunctionMouseUpOrMenu(key)){  //supress both mouse up and down reaction
529        widgContent.setEditable(true);
530        bEditable = true;
531        return true;
532      } else return false; 
533      // /
534    }
535  };
536
537
538  GralUserAction actionSave = new GralUserAction("actionSave")
539  {
540    @Override public boolean userActionGui(int keyCode, GralWidget infos, Object... params)
541    { 
542      if(FcmdView.this.bEditable){
543        try{
544        //InputStream inpBytes = new ByteArrayInputStream(uContent);
545        //InputStreamReader inpText = new InputStreamReader(inpBytes);
546        //BufferedReader inpLines = new BufferedReader(inpText);
547          FileRemote filedst = main.currentFile();
548          WritableByteChannel outchn =filedst.openWrite(0);  //use Channel-io to support remote files. 
549          ByteBuffer outBuffer = ByteBuffer.allocate(1200);
550          //Writer out = new FileWriter();
551          syncContentFromWidg();
552          int nrofBytes = zContent; //uContent.length;
553          int posBytes = 0;
554          while(nrofBytes > 0){
555            int zWrite = nrofBytes >=1200 ? 1200 : nrofBytes;
556            outBuffer.put(uContent, posBytes, zWrite);
557            nrofBytes -= zWrite;
558            posBytes += zWrite;
559            outBuffer.limit(zWrite);
560            outBuffer.rewind();
561            outchn.write(outBuffer);
562            outBuffer.limit(outBuffer.capacity());
563            outBuffer.clear();
564          }
565          outchn.close();
566          
567        } catch(Exception exc){
568          main._gralMng.writeLog(0, exc);
569        }
570      }
571      return true;
572      // /
573    }
574  };
575
576
577  GralUserAction actionSaveTextAsUTF8unix = new GralUserAction("actionSaveTextAsUTF8unix")
578  { @Override public boolean userActionGui(int keyCode, GralWidget infos, Object... params)
579    { saveTextAs(utf8, endl_0a);
580      return true;
581    }
582  };
583
584
585  GralUserAction actionSaveTextAsWindows = new GralUserAction("actionSaveTextAsWindows")
586  { @Override public boolean userActionGui(int keyCode, GralWidget infos, Object... params)
587    { saveTextAs(iso8859_1, endl_0d0a);
588      return true;
589    }
590  };
591
592
593  GralUserAction actionSaveTextAsISO8859_1_unix = new GralUserAction("actionSaveTextAsISO8859_1_unix")
594  { @Override public boolean userActionGui(int keyCode, GralWidget infos, Object... params)
595    { saveTextAs(iso8859_1, endl_0d0a);
596      return true;
597    }
598  };
599
600
601  GralUserAction actionOnSetInvisible = new GralUserAction("view-setInvisible")
602  { @Override public boolean exec(int keyCode, GralWidget_ifc widgi, Object... params)
603    { bVisible = false;
604      nrQuickview = 0;
605      btnQuickview.setState(GralButton.State.Off);
606      return true;
607    }
608  };
609
610
611  
612  GralTextFieldUser_ifc userKeys = new GralTextFieldUser_ifc() {
613    
614    @Override
615    public boolean userKey(int keyCode, String content, int cursorPos, int selectStart, int selectEnd) {
616      boolean bDone = true;
617      widgShowInfo.setText("" + cursorPos);
618      switch(keyCode){
619      case KeyCode.ctrl + 'F': actionFind.userActionGui(KeyCode.menuEntered, null); break;
620        default: bDone = false;
621      }
622      return bDone;
623    }
624  };
625  
626}