    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}