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}