package de.persosim.simulator.ui.parts; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.eclipse.e4.ui.di.Focus; import org.eclipse.e4.ui.di.UISynchronize; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Slider; import org.eclipse.swt.widgets.Text; import de.persosim.driver.connector.service.NativeDriverConnector; import de.persosim.simulator.ui.Activator; import de.persosim.simulator.ui.handlers.SelectPersoFromFileHandler; import de.persosim.simulator.ui.utils.LinkedListLogListener; /** * @author slutters * */ public class PersoSimPart { public static final String DE_PERSOSIM_SIMULATOR_BUNDLE = "de.persosim.simulator"; public static final String PERSO_PATH = "personalization/profiles/"; public static final String PERSO_FILE = "Profile01.perso"; public static final int LOG_LIMIT = 1000; // get UISynchronize injected as field @Inject UISynchronize sync; private Text txtOutput; private Thread uiThread = null; private Thread updateThread = null; //maximum amount of strings saved in the buffer public static final int MAXIMUM_CACHED_CONSOLE_LINES = 2000; //maximum of lines the text field can show int maxLineCount=0; Composite parent; private Button lockScroller; Boolean locked = false; Slider slider; @PostConstruct public void createComposite(Composite parentComposite) { parent = parentComposite; parent.setLayout(new GridLayout(2, false)); //add console out txtOutput = createConsoleOut(parent); addConsoleOutMenu(txtOutput); //configure the slider slider = createSlider(parent); txtOutput.addMouseWheelListener(new MouseWheelListener() { @Override public void mouseScrolled(MouseEvent e) { int count = e.count; slider.setSelection(slider.getSelection()-count); buildNewConsoleContent(); } }); txtOutput.addKeyListener(new KeyListener() { @Override public void keyReleased(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { if(e.keyCode == SWT.ARROW_DOWN){ slider.setSelection(slider.getSelection()+1); } else if(e.keyCode == SWT.ARROW_UP){ slider.setSelection(slider.getSelection()-1); } buildNewConsoleContent(); } }); txtOutput.addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { //if the size of the text field changes the shown output should be readjusted final LinkedListLogListener listener = Activator.getListLogListener(); if (listener == null) { txtOutput.setText( "The OSGi logging service can not be used.\nPlease check the availability and OSGi configuration" + System.lineSeparator()); } else { buildNewConsoleContent(); showNewOutput(); } } }); parent.setLayout(new GridLayout(2, false)); createConsoleIn(parent); lockScroller = new Button(parent, SWT.TOGGLE); lockScroller.setText(" lock "); lockScroller.addListener(SWT.Selection, new Listener() { @Override public void handleEvent (Event e) { if(locked){ lockScroller.setText(" lock "); locked=false; }else{ lockScroller.setText("unlock"); locked=true; } } }); Activator.addLogListener(); updateThread = createUpdateThread(); updateThread.setDaemon(true); updateThread.start(); } private Thread createUpdateThread() { final LinkedListLogListener listener = Activator.getListLogListener(); if (listener == null){ txtOutput.setText("The OSGi logging service can not be used.\nPlease check the availability and OSGi configuration" + System.lineSeparator()); } uiThread = Display.getCurrent().getThread(); final Thread updateThread = new Thread() { public void run() { while (!isInterrupted() && uiThread.isAlive()) { sync.syncExec(new Runnable() { @Override public void run() { try { if (checkForRefresh(listener)) { listener.resetRefreshState(); buildNewConsoleContent(); Thread.sleep(50); showNewOutput(); } else { Thread.sleep(50); } } catch (InterruptedException e) { // sleep got interrupted } } }); } } }; return updateThread; } /** * Checks if a refresh of the logging part is necessary. * * @return true for refresh and false for no refresh */ private boolean checkForRefresh(LinkedListLogListener listener){ if(!txtOutput.isDisposed()){ //it is disposed when view not active if(listener.isRefreshNeeded() && !locked){ return true; } else if(txtOutput.getText().isEmpty() && listener.getNumberOfCachedLines()>0){ //empty log happens when view was closed and opened again -> force a refresh return true; } } return false; } /** * This method creates the slider to be used in connection with the console output. * @param parentComposite a composite control which will be the parent of the new instance (cannot be null) * @return the slider to be used in connection with the console output */ private Slider createSlider(Composite parentComposite) { Slider slider = new Slider(parentComposite, SWT.V_SCROLL); slider.setIncrement(1); slider.setPageIncrement(10); if (Activator.getListLogListener() != null){ slider.setMaximum(Activator.getListLogListener().getNumberOfCachedLines()+slider.getThumb()); } slider.setMinimum(0); slider.setLayoutData(new GridData(GridData.FILL_VERTICAL)); SelectionListener sliderListener = new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { buildNewConsoleContent(); } }; slider.addSelectionListener(sliderListener); return slider; } /** * This function adds a new menu entry to the logging part. */ private void addConsoleOutMenu(Text console) { Menu consoleMenu = createConsoleMenu(console); console.setMenu(consoleMenu); } /** * This method creates the console input. * @param parent a composite control which will be the parent of the new instance (cannot be null) * @return the console input */ private Text createConsoleIn(Composite parent) { final Text txtIn = new Text(parent, SWT.BORDER); txtIn.setMessage("Enter command here"); txtIn.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if((e.character == SWT.CR) || (e.character == SWT.LF)) { String line = txtIn.getText(); Activator.executeUserCommands(line); txtIn.setText(""); } } }); txtIn.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); return txtIn; } /** * This method creates the console output. * @param compositeParent a composite control which will be the parent of the new instance (cannot be null) * @return the console output */ private Text createConsoleOut(Composite compositeParent) { Text txtOut = new Text(compositeParent, SWT.READ_ONLY | SWT.BORDER | SWT.H_SCROLL | SWT.MULTI); txtOut.setEditable(false); txtOut.setCursor(null); txtOut.setLayoutData(new GridData(GridData.FILL_BOTH)); txtOut.setSelection(txtOut.getText().length()); txtOut.setTopIndex(txtOut.getLineCount() - 1); return txtOut; } /** * This method creates the console output menu. * @param controlParent a composite control which will be the parent of the new instance (cannot be null) * @return the console output menu */ private Menu createConsoleMenu(Control controlParent) { final Control controlParentFinal = controlParent; Menu consoleMenu = new Menu(controlParentFinal); //configure log level menu MenuItem changeLogLevelItem = new MenuItem(consoleMenu, SWT.CASCADE); changeLogLevelItem.setText("Configure logLevel"); changeLogLevelItem.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { LogLevelDialog ld = new LogLevelDialog(null); ld.open(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); //configure load personalization menu MenuItem selectPersonalization = new MenuItem(consoleMenu, SWT.CASCADE); selectPersonalization.setText("Load Personalization"); selectPersonalization.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { SelectPersoFromFileHandler fileHandler = new SelectPersoFromFileHandler(); fileHandler.execute(controlParentFinal.getShell()); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); // configure save log menu MenuItem saveLogItem = new MenuItem(consoleMenu, SWT.CASCADE); saveLogItem.setText("Save log to file"); saveLogItem.addSelectionListener(new SelectionListener() { String logFileName; File file; PrintWriter writer; @Override public void widgetSelected(SelectionEvent e) { try{ logFileName = "PersoSim_" + new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime()) + ".log"; file = new File(logFileName); writer = new PrintWriter(file); for (int i = 0; i < Activator.getListLogListener() .getNumberOfCachedLines(); i++) { writer.write(Activator.getListLogListener().getLine(i)+"\n"); } MessageDialog.openInformation(txtOutput.getShell(), "Info", "Logfile written to " + file.getAbsolutePath()); }catch(IOException ioe){ ioe.printStackTrace(); } finally { if (writer != null){ writer.flush(); writer.close(); } } } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); return consoleMenu; } /** * changes the maximum of the slider and selects the * new Maximum to display the latest log messages */ private void rebuildSlider(){ sync.syncExec(new Runnable() { @Override public void run() { if (Activator.getListLogListener() != null) { slider.setMaximum(Activator.getListLogListener() .getNumberOfCachedLines() + slider.getThumb() - maxLineCount + 1); } } }); } /** * takes the selected value from the Slider and prints the fitting messages * from the LinkedList until the Text field is full */ private void buildNewConsoleContent() { if (Activator.getListLogListener() == null){ // if there is no log listener there is no content to be printed. return; } // clean text field before filling it with the requested data final StringBuilder strConsoleStrings = new StringBuilder(); // calculates how many lines can be shown without cutting maxLineCount = ( txtOutput.getBounds().height - txtOutput.getHorizontalBar().getThumbBounds().height ) / txtOutput.getLineHeight(); //synchronized is used to avoid IndexOutOfBoundsExceptions synchronized (Activator.getListLogListener()) { int listSize = Activator.getListLogListener().getNumberOfCachedLines(); // value is needed to stop writing in the console when the end in // the list is reached try { int linesToShow = listSize - slider.getMaximum() + slider.getThumb(); int sliderSelection = slider.getSelection(); // Fill text field with selected data for (int i = 0; i < linesToShow; i++) { strConsoleStrings.append(Activator.getListLogListener().getLine(sliderSelection + i)); strConsoleStrings.append("\n"); } } catch ( Exception e) {}//IMPL PokémonException } // send the StringBuilder data to the console field sync.syncExec(new Runnable() { @Override public void run() { try { txtOutput.setText(strConsoleStrings.toString()); } catch(Exception e) { //IMPL PokémonException } } }); } /** * controls slider selection (auto scrolling) */ public void showNewOutput() { if (uiThread.isAlive() && !uiThread.isInterrupted()) { sync.syncExec(new Runnable() { @Override public void run() { rebuildSlider(); slider.setSelection(slider.getMaximum()); } }); } } /** * This method closes the part and interrupts all threads if needed. */ @PreDestroy public void closePersoSimView() { if(updateThread.isAlive()) { updateThread.interrupt(); } Activator.removeLogListener(); } @Focus public void setFocus() { txtOutput.setFocus(); } @Override public String toString() { return "OutputHandler here!"; } /** * Attach reader to simulator, i.e. connect connector */ public void connectReader(NativeDriverConnector connector) { try { connector.connect("localhost", 5678); } catch (IOException e) { MessageDialog.openError(parent.getShell(), "Error", "Failed to connect to virtual card reader driver!\nTry to restart driver, then re-connect by selecting\ndesired reader type from menu \"Reader Type\"."); } } /** * Separate reader from simulator, i.e. disconnect connector */ public void disconnectReader(NativeDriverConnector connector) { if ((connector != null) && (connector.isRunning())) { try { connector.disconnect(); } catch (IOException | InterruptedException e) { e.printStackTrace(); MessageDialog.openError(parent.getShell(), "Error", "Failed to disconnect reader"); } } } }