diff --git a/plugins/TestingList.form b/plugins/TestingList.form index e1d9722..66dc9e3 100644 --- a/plugins/TestingList.form +++ b/plugins/TestingList.form @@ -34,7 +34,7 @@ - + diff --git a/plugins/TestingList.java b/plugins/TestingList.java index 4ea1436..87cbbd0 100644 --- a/plugins/TestingList.java +++ b/plugins/TestingList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Paul Scherrer Institute. All rights reserved. + * Copyright (c) 2015 Paul Scherrer Institute. All rights reserved. */ import ch.psi.utils.swing.MonitoredPanel; @@ -28,6 +28,10 @@ import javax.swing.JFileChooser; import javax.swing.JFrame; import org.apache.commons.io.FilenameUtils; +/** + * + * @author boccioli_m + */ public class TestingList extends Panel { NetbeansPluginPanel testingList; @@ -39,6 +43,10 @@ public class TestingList extends Panel { } }; + /** + * + * @return + */ @Override protected JPanel create() { try { @@ -50,8 +58,13 @@ public class TestingList extends Panel { return testingList; } - //listen to script end of execution and get return value - @Override + /** + * listen to script end of execution and get return value + * @param fileName + * @param result + * @param exception + */ + @Override protected void onExecutedFile(String fileName, Object result, Throwable exception) { int iCurrentTestPos = 0; try { @@ -106,9 +119,10 @@ public class TestingList extends Panel { } } - - //table column indexes - public enum COL { + /** + *enumeration of table column indexes + */ + public enum COL { CHECK (0), TIME (1), DEVICENAME (2), @@ -118,30 +132,39 @@ public class TestingList extends Panel { TESTPATH (6), TESTPARAMS (7), TESTDESCR (8), - RESULT (9), - STATUS (10), - ICON (11); + TESTHELP (9), + RESULT (10), + STATUS (11), + ICON (12); private int value; private COL(int value) { this.value = value; } + /** + * + * @return + */ public int index(){ return (int) value; } }; - //enumeration of possible test statuses: text and related icon - public enum TestStatus { + /** + *enumeration of possible test statuses: text and related icon + */ + public enum TestStatus { SUCCESS, FAILURE, PENDING, DISABLED, RUNNING; - - + /** + * + * @return + */ public String IconFilename(){ String iconFileName = ""; switch (this){ @@ -187,6 +210,10 @@ public class TestingList extends Panel { return status; } + /** + * + * @return + */ public ImageIcon Icon(){ String iconFileName = this.IconFilename(); ImageIcon icon = null; @@ -196,7 +223,10 @@ public class TestingList extends Panel { } }; - public class NetbeansPluginPanel extends MonitoredPanel { + /** + * + */ + public class NetbeansPluginPanel extends MonitoredPanel { Logger logger = Logger.getLogger("TestsLog"); //these paths are converted to unix or win path according to host OS private final String TESTS_DEVICES_DEFAULT_DIR = new java.io.File(".").getCanonicalPath() @@ -223,6 +253,10 @@ public class TestingList extends Panel { } } + /** + * + * @throws IOException + */ public NetbeansPluginPanel() throws IOException { initComponents(); initLogger(); @@ -253,14 +287,14 @@ public class TestingList extends Panel { }, new String [] { - "Select", "Time", "Device Name", "Device Description", "Test Suite", "Test Name", "Test Peth", "Test Parameters", "Test Description", "Last Test Result", "Status", "" + "Select", "Time", "Device Name", "Device Description", "Test Suite", "Test Name", "Test Peth", "Test Parameters", "Test Description", "Test Help", "Last Test Result", "Status", "" } ) { Class[] types = new Class [] { - java.lang.Boolean.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, javax.swing.Icon.class + java.lang.Boolean.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, javax.swing.Icon.class }; boolean[] canEdit = new boolean [] { - true, false, false, false, false, false, false, false, false, false, false, false + true, false, false, false, false, false, false, false, false, false, false, false, false }; public Class getColumnClass(int columnIndex) { @@ -462,8 +496,11 @@ public class TestingList extends Panel { private javax.swing.JTable jTable1; // End of variables declaration//GEN-END:variables // - - //return status of run button. True = tests launching sequence is running + + /** + * + * @return status of run button. True = tests launching sequence is running + */ public boolean isTestRunAllowed(){ return this.jButtonRun.getToolTipText().equals("Stop tests"); } @@ -486,18 +523,18 @@ public class TestingList extends Panel { //open details of the selected test in a new panel private void openDetails() throws Exception{ - String sDeviceName, sTestName, sTestCaseName, sDeviceDescription, sTestDescription, sLastResult, sResultTime; - HashMap mParameters = new HashMap(); + //pick details from the clicked row int row = jTable1.getSelectedRow(); - sDeviceName = jTable1.getValueAt(row, COL.DEVICENAME.ordinal()).toString(); - sTestName = jTable1.getValueAt(row, COL.TESTNAME.ordinal()).toString(); - sTestCaseName = jTable1.getValueAt(row, COL.TESTSUITE.ordinal()).toString(); - sTestDescription = jTable1.getValueAt(row, COL.TESTDESCR.ordinal()).toString(); - sDeviceDescription = jTable1.getValueAt(row, COL.DEVICEDESCR.ordinal()).toString(); - sLastResult = jTable1.getValueAt(row, COL.RESULT.ordinal()).toString(); - sResultTime = jTable1.getValueAt(row, COL.TIME.ordinal()).toString(); - mParameters = buildParametersMap(String.valueOf(jTable1.getValueAt(row, COL.TESTPARAMS.ordinal()))); + String sDeviceName = jTable1.getValueAt(row, COL.DEVICENAME.ordinal()).toString(); + String sTestName = jTable1.getValueAt(row, COL.TESTNAME.ordinal()).toString(); + String sTestCaseName = jTable1.getValueAt(row, COL.TESTSUITE.ordinal()).toString(); + String sTestDescription = jTable1.getValueAt(row, COL.TESTDESCR.ordinal()).toString(); + String sDeviceDescription = jTable1.getValueAt(row, COL.DEVICEDESCR.ordinal()).toString(); + String sLastResult = jTable1.getValueAt(row, COL.RESULT.ordinal()).toString(); + String sResultTime = jTable1.getValueAt(row, COL.TIME.ordinal()).toString(); + String sTestHelp = String.valueOf(jTable1.getValueAt(row, COL.TESTHELP.ordinal())); + HashMap mParameters = buildParametersMap(String.valueOf(jTable1.getValueAt(row, COL.TESTPARAMS.ordinal()))); //create map for passing details to Details Panel HashMap details = new HashMap(); details.put("deviceName", sDeviceName); @@ -508,6 +545,7 @@ public class TestingList extends Panel { details.put("testResult", sLastResult); details.put("time", sResultTime); details.put("parameters", mParameters); + details.put("testHelp", sTestHelp); //open details panel JDialog dlg = new JDialog(getView(), "Test Details - " + sTestName , true); //create a class to visualise the details panel @@ -519,8 +557,15 @@ public class TestingList extends Panel { dlg.pack(); dlg.setVisible(true); } - - //show test result in table + + /** + *show test result in table + * @param deviceName + * @param testName + * @param res + * @param status + * @return + */ public int showResult(String deviceName, String testName, String res, String status) { int rowD = -1; String sTestName = testName; @@ -560,8 +605,11 @@ public class TestingList extends Panel { } return rowD; } - - //find the test currently in progress + + /** + *find the test currently in progress + * @return properties of the test in progress + */ public String[] getTestInProgress() { String[] dsTestProperties = {"",""}; //search for device name in table @@ -606,7 +654,9 @@ public class TestingList extends Panel { return sStatus; } - //visualise test status (columns status and icon) + /** + *visualise test status (columns status and icon) + */ public void updateStatus() { String sStatus; boolean bSelected; @@ -683,7 +733,9 @@ public class TestingList extends Panel { } } - //table management + /** + *table management + */ public void buildTable() { String sDate = getNow(); DefaultTableModel model = (DefaultTableModel) jTable1.getModel(); @@ -710,7 +762,8 @@ public class TestingList extends Panel { String testSuite, String testName, String testParams, - String testDescription) { + String testDescription, + String testHelp) { String sDate = ""; if (testName.equals("") || deviceName.equals("")) { return; @@ -718,19 +771,26 @@ public class TestingList extends Panel { ImageIcon icon = null;// new ImageIcon(getClass().getResource("/icons/button_pause-16px.png")); DefaultTableModel model = (DefaultTableModel) jTable1.getModel(); String testPath = FilenameUtils.separatorsToSystem(TESTS_TESTS_DEFAULT_DIR + testSuite + "/" + testName + "/" + testName + ".py"); - model.addRow(new Object[]{false, sDate, deviceName, deviceDescription, testSuite, testName, testPath, testParams, testDescription, "", "Pending", icon}); + model.addRow(new Object[]{false, sDate, deviceName, deviceDescription, testSuite, testName, testPath, testParams, testDescription, testHelp, "", "Pending", icon}); jTable1.setModel(model); updateStatus(); } - //formatted time + /** + * + * @return formatted time + */ public String getNow() { DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); Date date = new Date(); return dateFormat.format(date); } - //time without format + + /** + * + * @return time with sortable format + */ public String getnow() { DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(); @@ -769,12 +829,13 @@ public class TestingList extends Panel { if (configFile.exists() && !configFile.isDirectory()) { InputStream ist = new FileInputStream(configFile); propTest.load(ist); - addToTable(propDevice.getProperty("name"), - propDevice.getProperty("description"), - propDevice.getProperty("tests"), - propTest.getProperty("name"), - propTest.getProperty("parameters"), - propTest.getProperty("description")); + addToTable( propDevice.getProperty("name"), + propDevice.getProperty("description"), + propDevice.getProperty("tests"), + propTest.getProperty("name"), + propTest.getProperty("parameters"), + propTest.getProperty("description"), + propTest.getProperty("help")); iCounter++; } } @@ -786,25 +847,23 @@ public class TestingList extends Panel { logger.log(Level.INFO, iCounter + " tests loaded."); } - // - /* - Build a map with optional parameters to be passed to the testing script. - The map is like this: - parameters - | - \_ name - | | - | \_ value - | \_ description - | - \_ name - | | - | \_ value - | \_ description - ... - the name 'name' is the mapping key. 'value' and 'description' are constant mapping keys of a nested map. - */ - // + /** + * Build a map with optional parameters to be passed to the testing script. + * The map is like this: + *parameters + * | + * \_ name + * | | + * | \_ value + * | \_ description + * | + * \_ name + * | | + * | \_ value + * | \_ description + * ... + * the name 'name' is the mapping key. 'value' and 'description' are constant mapping keys of a nested map. + */ private HashMap buildParametersMap(String parametersString){ HashMap mParameters = new HashMap(); // contains name and attributes HashMap mParameterAttributes = new HashMap(); //contians value and description @@ -823,6 +882,9 @@ public class TestingList extends Panel { return mParameters; } + /** + * + */ public void selectFile() { final JFileChooser fc = new JFileChooser(); diff --git a/plugins/TestingListDetails.form b/plugins/TestingListDetails.form index e551be1..468f93a 100644 --- a/plugins/TestingListDetails.form +++ b/plugins/TestingListDetails.form @@ -19,97 +19,98 @@ - - - - - - - - + + - - - - - - - + + + + + - - - - - - + + + + + - + + - + - + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -119,50 +120,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -175,6 +203,9 @@ + + + @@ -202,6 +233,7 @@ + @@ -211,6 +243,9 @@ + + + @@ -237,11 +272,19 @@ + + + + + + + + @@ -252,6 +295,9 @@ + + + @@ -260,10 +306,18 @@ + + + + + + + + @@ -274,11 +328,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/TestingListDetails.java b/plugins/TestingListDetails.java index a3a2243..af0cb5d 100644 --- a/plugins/TestingListDetails.java +++ b/plugins/TestingListDetails.java @@ -1,4 +1,8 @@ +/* + * Copyright (c) 2015 Paul Scherrer Institute. All rights reserved. + */ +import java.io.File; import java.util.HashMap; import javax.swing.ImageIcon; import javax.swing.table.DefaultTableModel; @@ -57,29 +61,42 @@ public class TestingListDetails extends javax.swing.JPanel { jLabel8 = new javax.swing.JLabel(); jScrollPane3 = new javax.swing.JScrollPane(); txtTestDescription = new javax.swing.JTextArea(); + jLabel3 = new javax.swing.JLabel(); + jScrollPane4 = new javax.swing.JScrollPane(); + jEditorPaneHelp = new javax.swing.JEditorPane(); + jLabel1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel1.setText("Device name"); txtDeviceName.setEditable(false); + txtDeviceName.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtDeviceName.setText("jTextField1"); + jLabel2.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel2.setText("Device Description"); txtDeviceDescription.setEditable(false); + txtDeviceDescription.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtDeviceDescription.setText("jTextField2"); + jLabel4.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel4.setText("Test Case/Suite"); txtTestSuite.setEditable(false); + txtTestSuite.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtTestSuite.setText("jTextField2"); + jLabel5.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel5.setText("Test Name"); txtTestName.setEditable(false); + txtTestName.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtTestName.setText("jTextField2"); + jLabel6.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel6.setText("Last Test Result"); + jTableParams.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jTableParams.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { @@ -97,6 +114,7 @@ public class TestingListDetails extends javax.swing.JPanel { } }); jTableParams.setDragEnabled(true); + jTableParams.setRowHeight(20); jScrollPane1.setViewportView(jTableParams); if (jTableParams.getColumnModel().getColumnCount() > 0) { jTableParams.getColumnModel().getColumn(0).setMinWidth(150); @@ -106,6 +124,7 @@ public class TestingListDetails extends javax.swing.JPanel { jTableParams.getColumnModel().getColumn(1).setMaxWidth(100); } + jLabel7.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel7.setText("Test Parameters"); cmCancel.setText("Cancel"); @@ -125,21 +144,40 @@ public class TestingListDetails extends javax.swing.JPanel { this.cmCancel.setVisible(false); this.cmOk.setVisible(false); this.cmDefault.setVisible(false); + cmDefault.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmDefaultActionPerformed(evt); + } + }); + + jScrollPane2.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtTestResult.setEditable(false); txtTestResult.setColumns(20); + txtTestResult.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtTestResult.setLineWrap(true); txtTestResult.setRows(5); jScrollPane2.setViewportView(txtTestResult); + jLabel8.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel8.setText("Test Description"); + jScrollPane3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N + txtTestDescription.setEditable(false); txtTestDescription.setColumns(20); + txtTestDescription.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N txtTestDescription.setLineWrap(true); txtTestDescription.setRows(5); jScrollPane3.setViewportView(txtTestDescription); + jLabel3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N + jLabel3.setText("Help"); + + jEditorPaneHelp.setContentType("text/html"); // NOI18N + jEditorPaneHelp.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N + jScrollPane4.setViewportView(jEditorPaneHelp); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -147,36 +185,37 @@ public class TestingListDetails extends javax.swing.JPanel { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(427, 427, 427)) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(383, 383, 383)) + .addComponent(jLabel4) + .addComponent(jLabel5) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jLabel1) + .addComponent(jLabel2) .addComponent(jLabel7) - .addComponent(jLabel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jLabel8, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jLabel6) + .addComponent(jLabel8)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(txtDeviceName) .addComponent(txtDeviceDescription) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 673, Short.MAX_VALUE) .addComponent(txtTestSuite) - .addComponent(txtTestName, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(txtTestName) .addComponent(jScrollPane3) .addComponent(jScrollPane2) - .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 513, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(cmDefault, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(cmOk, javax.swing.GroupLayout.PREFERRED_SIZE, 96, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(cmCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap(21, Short.MAX_VALUE)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE))))) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel3) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jScrollPane4, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE)) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -184,41 +223,42 @@ public class TestingListDetails extends javax.swing.JPanel { .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) - .addComponent(txtDeviceName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(txtDeviceName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel2) - .addComponent(txtDeviceDescription, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel4) - .addComponent(txtTestSuite, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel5) - .addComponent(txtTestName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel8) - .addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel6)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 188, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel7)) - .addGap(18, 18, 18) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(cmDefault, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(78, 78, 78)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(txtDeviceDescription, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(txtTestSuite, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel5) + .addComponent(txtTestName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel8) + .addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 188, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel7)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 65, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cmCancel) + .addComponent(cmOk) + .addComponent(cmDefault))) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(cmOk, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(cmCancel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(jScrollPane4) + .addContainerGap()))) ); }// //GEN-END:initComponents @@ -229,6 +269,8 @@ public class TestingListDetails extends javax.swing.JPanel { this.txtTestSuite.setText(hDetails.get("testSuite").toString()); this.txtTestName.setText(hDetails.get("testName").toString()); this.txtTestResult.setText(hDetails.get("time").toString() + "\n" + hDetails.get("testResult").toString()); + this.jEditorPaneHelp.setText(String.valueOf(hDetails.get("testHelp"))); + //parameters table HashMap hParams = (HashMap) hDetails.get("parameters"); String name="", value="", description=""; @@ -251,13 +293,19 @@ public class TestingListDetails extends javax.swing.JPanel { }//GEN-LAST:event_cmCancelActionPerformed + private void cmDefaultActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmDefaultActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmDefaultActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cmCancel; private javax.swing.JButton cmDefault; private javax.swing.JButton cmOk; + private javax.swing.JEditorPane jEditorPaneHelp; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JLabel jLabel5; private javax.swing.JLabel jLabel6; @@ -266,6 +314,7 @@ public class TestingListDetails extends javax.swing.JPanel { private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane2; private javax.swing.JScrollPane jScrollPane3; + private javax.swing.JScrollPane jScrollPane4; private javax.swing.JTable jTableParams; private javax.swing.JTextField txtDeviceDescription; private javax.swing.JTextField txtDeviceName; diff --git a/script/tests/tests/Collimator Tests/Calibrate/.config b/script/tests/tests/Collimator Tests/Calibrate/.config index e75c90e..2d54ba8 100644 --- a/script/tests/tests/Collimator Tests/Calibrate/.config +++ b/script/tests/tests/Collimator Tests/Calibrate/.config @@ -1,3 +1,14 @@ -name=Calibrate -description=Calibrates the device -filename=Calibrate.xml +name=Calibrate +description=Calibrates the device +filename=Calibrate.xml +help = \ +This test sends a command to the low level firmware which controls the collimators \n\ +requesting that it calibrates itself. \n\n\ +Calibration involves moving to the R1 and R2 reference positions and measuring the \n\ +number of steps required to do so. At the end of the sequence the default collimator \n\ +will be selected. \n\n\ +During the course of the expected calibration period (45-70 seconds) the test \n\ +procedure will plot the values of all critical system variables. \n\n\ +For further information please consult Valery Ovinnikov. + + \ No newline at end of file diff --git a/script/tests/tests/Collimator Tests/Calibrate/Calibrate.py b/script/tests/tests/Collimator Tests/Calibrate/Calibrate.py index 1cfbe9c..b3b25c7 100644 --- a/script/tests/tests/Collimator Tests/Calibrate/Calibrate.py +++ b/script/tests/tests/Collimator Tests/Calibrate/Calibrate.py @@ -1,90 +1,69 @@ #Script imported from: Calibrate.xml -ret = 'Test failed' +ret = 'Calibration failed' status = False -#Pre-actions try: - caput('PO2DV-NCS-'+DEVICE+':INIT.PROC', '1') + #Pre-actions: 1 = calibrate + caput(DEVICE+':COM:2', 1) + #Creating channels: dimension 1 + #PseudoPositioner id000000 + #ScalarDetector id000001 + id000001 = Channel(DEVICE+':STA:1', type = 'd') + #ScalarDetector id000003 + id000003 = Channel(DEVICE+':IST:2', type = 'd') + #ScalarDetector id000004 + id000004 = Channel(DEVICE+':DIAM:2', type = 'd') + #ScalarDetector id000005 + id000005 = Channel(DEVICE+':IST1:1', type = 'd') + #ScalarDetector id000006 + id000006 = Channel(DEVICE+':IST1:2', type = 'd') + #ScalarDetector id000007 + id000007 = Channel(DEVICE+':IST2:1', type = 'd') + #ScalarDetector id000008 + id000008 = Channel(DEVICE+':IST2:2', type = 'd') + #ScalarDetector id000009 + id000009 = Channel(DEVICE+':IST3:1', type = 'd') + #ScalarDetector id000010 + id000010 = Channel(DEVICE+':IST3:2', type = 'd') except: - print "Unexpected error:", sys.exc_info()[0] - ret = 'Unable to create channel' - success = False - raise - sys.exit() + print "Unexpected error:", sys.exc_info()[0] + ret = 'Unable to create channel - ' + traceback.format_exc() + success = False + raise Exception('Unable to create channel - ' + traceback.format_exc()) + sys.exit() #TODO: Set the diplay names of positioners and detectors -scan = ManualScan(['id000000'], ['id000001', 'id000002', 'id000003', 'id000004', 'id000005', 'id000006', 'id000007', 'id000008', 'id000009', 'id000010', 'idResult'] , [0.0], [1000.0], [1000]) +scan = ManualScan(['id000000'], ['id000001', 'id000003', 'id000004', 'id000005', 'id000006', 'id000007', 'id000008', 'id000009', 'id000010'] , [0.0], [900.0], [900]) scan.start() - -#Creating channels: dimension 1 -#PseudoPositioner id000000 -#ScalarDetector id000001 -id000001 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.MSTA', type = 'd') -#ScalarDetector id000002 -id000002 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.RVAL', type = 'd') -#ScalarDetector id000003 -id000003 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.VAL', type = 'd') -#ScalarDetector id000004 -id000004 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.ATHM', type = 'd') -#ScalarDetector id000005 -id000005 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.LLS', type = 'd') -#ScalarDetector id000006 -id000006 = Channel('PO2DV-NCS-'+DEVICE+':MOTOR.HLS', type = 'd') -#ScalarDetector id000007 -id000007 = Channel('PO2DV-NCS-'+DEVICE+':ENCODERraw', type = 'd') -#ScalarDetector id000008 -id000008 = Channel('PO2DV-NCS-'+DEVICE+':ENCODER', type = 'd') -#ScalarDetector id000009 -id000009 = Channel('PO2DV-NCS-'+DEVICE+':RDY', type = 'd') -#ScalarDetector id000010 -id000010 = Channel('PO2DV-NCS-'+DEVICE+':ILK', type = 'd') - + #Dimension 1 #PseudoPositioner id000000 -for setpoint1 in range(0, 1000): +for setpoint1 in range(0, 900): readback1 = setpoint1 - sleep( 0.05 ) # Settling time + sleep( 0.1 ) # Settling time #Detector id000001 detector1 = id000001.get() - #Detector id000002 - detector2 = id000002.get() #Detector id000003 - detector3 = id000003.get() + detector2 = id000003.get() #Detector id000004 - detector4 = id000004.get() + detector3 = id000004.get() #Detector id000005 - detector5 = id000005.get() + detector4 = id000005.get() #Detector id000006 - detector6 = id000006.get() + detector5 = id000006.get() #Detector id000007 - detector7 = id000007.get() + detector6 = id000007.get() #Detector id000008 - detector8 = id000008.get() + detector7 = id000008.get() #Detector id000009 - detector9 = id000009.get() + detector8 = id000009.get() #Detector id000010 - detector10 = id000010.get() - #Manipulation idResult - #Variable Mappings - ready = detector9 - interlock = detector10 - count = setpoint1 - if count < 800: - idResult = (0, "Note: the "+DEVICE+" calibration procedure did not complete.") - if ready == 1 and interlock == 1: - #print "The "+DEVICE+" drive was successfully initialised. The RDY and ILK signals indicate the drive is ready." - ret = 'Drive successfully initialised' - status = True - else: - #print "The RS calibration procedure failed. The RDY and ILK signals indicate the drive was NOT ready at the expected time (after 40s)." - ret = 'The RDY and ILK signals indicate the drive was NOT ready at the expected time (after 40s).' - status = False - scan.append ([setpoint1], [readback1], [detector1, detector2, detector3, detector4, detector5, detector6, detector7, detector8, detector9, detector10, idResult]) + detector9 = id000010.get() + scan.append ([setpoint1], [readback1], [detector1, detector2, detector3, detector4, detector5, detector6, detector7, detector8, detector9]) #Closing channels id000001.close() -id000002.close() id000003.close() id000004.close() id000005.close() @@ -95,3 +74,6 @@ id000009.close() id000010.close() scan.end() + +ret = 'Calibration done' +status = True diff --git a/script/tests/tests/Collimator Tests/Calibrate/Calibrate.xml b/script/tests/tests/Collimator Tests/Calibrate/Calibrate.xml index eef74b6..7e5028f 100644 --- a/script/tests/tests/Collimator Tests/Calibrate/Calibrate.xml +++ b/script/tests/tests/Collimator Tests/Calibrate/Calibrate.xml @@ -1,73 +1,61 @@ - - - - - - - - - - - - 1000 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + 900 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/script/tests/tests/Collimator Tests/Calibrate/doit.sh b/script/tests/tests/Collimator Tests/Calibrate/doit.sh deleted file mode 100644 index 4807a72..0000000 --- a/script/tests/tests/Collimator Tests/Calibrate/doit.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -sleep 5 diff --git a/script/tests/tests/Collimator Tests/Calibrate3/.config b/script/tests/tests/Collimator Tests/Calibrate3/.config deleted file mode 100644 index b2216fa..0000000 --- a/script/tests/tests/Collimator Tests/Calibrate3/.config +++ /dev/null @@ -1,12 +0,0 @@ -name=Calibrate3 -description=Calibrates the device -filename=Calibrate.xml -help = \ -This test sends a command to the low level firmware which controls the collimators \n\ -requesting that it calibrates itself. \n\n\ -Calibration involves moving to the R1 and R2 reference positions and measuring the \n\ -number of steps required to do so. At the end of the sequence the default collimator \n\ -will be selected. \n\n\ -During the course of the expected calibration period (45-70 seconds) the test \n\ -procedure will plot the values of all critical system variables. \n\n\ -For further information please consult Valery Ovinnikov. diff --git a/script/tests/tests/Collimator Tests/Calibrate3/Calibrate.xml b/script/tests/tests/Collimator Tests/Calibrate3/Calibrate.xml deleted file mode 100644 index 7e5028f..0000000 --- a/script/tests/tests/Collimator Tests/Calibrate3/Calibrate.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - 900 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/script/tests/tests/Collimator Tests/Calibrate3/Calibrate3.py b/script/tests/tests/Collimator Tests/Calibrate3/Calibrate3.py deleted file mode 100644 index 3010554..0000000 --- a/script/tests/tests/Collimator Tests/Calibrate3/Calibrate3.py +++ /dev/null @@ -1,67 +0,0 @@ -#Script imported from: Calibrate.xml - -#Pre-actions -caput('{DEVICE}:COM:2', 'CALLIBR') - -#TODO: Set the diplay names of positioners and detectors -scan = ManualScan(['id000000'], ['id000001', 'id000003', 'id000004', 'id000005', 'id000006', 'id000007', 'id000008', 'id000009', 'id000010'] , [0.0], [900.0], [900]) -scan.start() - -#Creating channels: dimension 1 -#PseudoPositioner id000000 -#ScalarDetector id000001 -id000001 = Channel('{DEVICE}:STA:1', type = 'd') -#ScalarDetector id000003 -id000003 = Channel('{DEVICE}:IST:2', type = 'd') -#ScalarDetector id000004 -id000004 = Channel('{DEVICE}:DIAM:2', type = 'd') -#ScalarDetector id000005 -id000005 = Channel('{DEVICE}:IST1:1', type = 'd') -#ScalarDetector id000006 -id000006 = Channel('{DEVICE}:IST1:2', type = 'd') -#ScalarDetector id000007 -id000007 = Channel('{DEVICE}:IST2:1', type = 'd') -#ScalarDetector id000008 -id000008 = Channel('{DEVICE}:IST2:2', type = 'd') -#ScalarDetector id000009 -id000009 = Channel('{DEVICE}:IST3:1', type = 'd') -#ScalarDetector id000010 -id000010 = Channel('{DEVICE}:IST3:2', type = 'd') - -#Dimension 1 -#PseudoPositioner id000000 -for setpoint1 in range(0, 900): - readback1 = setpoint1 - sleep( 0.1 ) # Settling time - #Detector id000001 - detector1 = id000001.get() - #Detector id000003 - detector2 = id000003.get() - #Detector id000004 - detector3 = id000004.get() - #Detector id000005 - detector4 = id000005.get() - #Detector id000006 - detector5 = id000006.get() - #Detector id000007 - detector6 = id000007.get() - #Detector id000008 - detector7 = id000008.get() - #Detector id000009 - detector8 = id000009.get() - #Detector id000010 - detector9 = id000010.get() - scan.append ([setpoint1], [readback1], [detector1, detector2, detector3, detector4, detector5, detector6, detector7, detector8, detector9]) - -#Closing channels -id000001.close() -id000003.close() -id000004.close() -id000005.close() -id000006.close() -id000007.close() -id000008.close() -id000009.close() -id000010.close() - -scan.end() diff --git a/script/tests/tests/Collimator Tests/Motor Test 1/.config b/script/tests/tests/Collimator Tests/Motor Test 1/.config index bef9ef4..72a62ed 100644 --- a/script/tests/tests/Collimator Tests/Motor Test 1/.config +++ b/script/tests/tests/Collimator Tests/Motor Test 1/.config @@ -1,7 +1,7 @@ name=Motor Test 1 -description=moves to CCW switch; then for M times moves to CW switch then CCW switch; between each M pauses for delay; log at CCW and CW -filename=Motor Test 3.xml +description=Moves to CCW switch; then for N times moves to CW switch then CCW switch; between each N pauses for delay; log at CCW and CW + #optional parameters. Description is compulsory. Syntax: -#parameters=::[;::] -parameters=repeatTimes:2:Repeat M times;delayS:5:Pause delay [s] +#parameters=::[;::] +parameters=repeatTimes:1:Repeat N times;delayS:5:Pause delay [s] diff --git a/script/tests/tests/Collimator Tests/Motor Test 1/Motor Test 1.py b/script/tests/tests/Collimator Tests/Motor Test 1/Motor Test 1.py index 7178c19..7de8032 100644 --- a/script/tests/tests/Collimator Tests/Motor Test 1/Motor Test 1.py +++ b/script/tests/tests/Collimator Tests/Motor Test 1/Motor Test 1.py @@ -1,5 +1,5 @@ #Script Motor Test 1 -#moves to CCW switch; then for M times moves N times to CW switch then CCW switch; between each M pauses for delay; log at CCW and CW +#Moves to CCW switch; then for M times moves N times to CW switch then CCW switch; between each M pauses for delay; log at CCW and CW import traceback @@ -84,7 +84,7 @@ start = startDefault #idInkr.get()+direction setpoint2 = end count = 0 print 'Starting test sequence' -for setpoint1 in range(0, loopTimes): +for setpoint1 in range(0, loopTimes*2): sleep( delaySeconds ) # Settling time #RegionPositioner idInkr idInkr.put(setpoint2, timeout=None) # TODO: Set appropriate timeout diff --git a/script/tests/tests/Collimator Tests/Motor Test 2/.config b/script/tests/tests/Collimator Tests/Motor Test 2/.config index 85feb3c..8926b72 100644 --- a/script/tests/tests/Collimator Tests/Motor Test 2/.config +++ b/script/tests/tests/Collimator Tests/Motor Test 2/.config @@ -1,7 +1,7 @@ name=Motor Test 2 description=Go to absolute position A, then move +B steps, then -2B steps, then +2Bsteps (ie oscillate round centre position, logging after each movement); repeat N times -filename=Motor Test 3.xml + #optional parameters. Description is compulsory. Syntax: -#parameters=::[;::] -parameters=repeatTimes:2:Repeat N times;midPoint:41.0:Middle point A;spanFromMidPoint:11.0:Span around middle point B +#parameters=::[;::] +parameters=repeatTimes:1:Repeat N times;midPoint:41.0:Middle point A;spanFromMidPoint:11.0:B steps around middle point A diff --git a/script/tests/tests/Collimator Tests/Motor Test 3/.config b/script/tests/tests/Collimator Tests/Motor Test 3/.config index b3e9757..78fbe26 100644 --- a/script/tests/tests/Collimator Tests/Motor Test 3/.config +++ b/script/tests/tests/Collimator Tests/Motor Test 3/.config @@ -1,7 +1,7 @@ name=Motor Test 3 -description=Moves from CCW to CW as a series of discrete translations (C times) logs after each translation. When end switch is encountered change direction. Repeat N times -filename=Motor Test 3.xml +description=Moves from CCW to CW as a series of discrete translations (C times) logs after each translation. When end switch is encountered change direction. Repeat N times + #optional parameters. Description is compulsory. Syntax: -#parameters=::[;::] -parameters=repeatTimes:2:Repeat M times;translation:2:Translation with +#parameters=::[;::] +parameters=repeatTimes:1:Repeat N times;translation:2:Translation C steps diff --git a/script/tests/tests/Collimator Tests/Motor Test 3/Motor Test 3.py b/script/tests/tests/Collimator Tests/Motor Test 3/Motor Test 3.py index ae8ada9..ba49b0d 100644 --- a/script/tests/tests/Collimator Tests/Motor Test 3/Motor Test 3.py +++ b/script/tests/tests/Collimator Tests/Motor Test 3/Motor Test 3.py @@ -87,7 +87,7 @@ start = idInkr.get()+direction countSteps = 0 print 'Starting testing sequence' count = 0 -for setpoint1 in range(0, loopTimes): +for setpoint1 in range(0, loopTimes*2): count = count + 1 sleep( 2 ) # Settling time #RegionPositioner idInkr diff --git a/script/tests/tests/Office Linear Slide Tests/power-supply/power-supply.py b/script/tests/tests/Office Linear Slide Tests/power-supply/power-supply.py index f3fc9e3..a8162b8 100644 --- a/script/tests/tests/Office Linear Slide Tests/power-supply/power-supply.py +++ b/script/tests/tests/Office Linear Slide Tests/power-supply/power-supply.py @@ -6,7 +6,7 @@ ret = 'Test failed' status = False DEVICE = 'PO2DV-NCS-VHQ1' -scan = ManualScan(['time'], ['SetVA', 'ActualVA', 'ActualIA'] , [0.0], [10.0], [10]) +scan = ManualScan(['time'], ['SetVA', 'ActualVA', 'ActualIA'] , [0.0], [20.0], [10]) scan.start() #Creating channels: dimension 1 @@ -41,13 +41,15 @@ for setpoint1 in frange(0.0, 120.0, 1.0, True): #Dimension 1 #LinearPositioner SetVA print 'Ramping up power supply' -for setpoint1 in frange(0.0, 10.0, 10.0, True): +for setpoint1 in frange(0.0, 20.0, 1.0, True): if setpoint1 > 50.0 or setpoint1 < 0.0: break SetVA.put(setpoint1, timeout=None) # TODO: Set appropriate timeout readback1 = SetVA.get() if abs(readback1 - setpoint1) > 0.5 : # TODO: Check accuracy raise Exception('Actor SetVA could not be set to the value ' + str(setpoint1)) + ret = 'SetVA could not be set to the value ' + str(setpoint1) + '(measured value: '+str(readback1)+')' + status = False break #scan quickly the output during some seconds for setpoint2 in range(0, 20):