package edu.ualberta.med.biobank.forms.linkassign; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; 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.Label; import org.eclipse.swt.widgets.Listener; import edu.ualberta.med.biobank.BiobankPlugin; import edu.ualberta.med.biobank.SessionManager; import edu.ualberta.med.biobank.common.action.Action; import edu.ualberta.med.biobank.common.action.scanprocess.CellInfo; import edu.ualberta.med.biobank.common.action.scanprocess.result.CellProcessResult; import edu.ualberta.med.biobank.common.action.scanprocess.result.ProcessResult; import edu.ualberta.med.biobank.common.action.scanprocess.result.ScanProcessResult; import edu.ualberta.med.biobank.common.util.RowColPos; import edu.ualberta.med.biobank.common.util.StringUtil; import edu.ualberta.med.biobank.common.wrappers.ContainerWrapper; import edu.ualberta.med.biobank.forms.utils.PalletScanManagement; import edu.ualberta.med.biobank.gui.common.BgcPlugin; import edu.ualberta.med.biobank.gui.common.widgets.BgcBaseText; import edu.ualberta.med.biobank.model.ContainerType; import edu.ualberta.med.biobank.validators.ScannerBarcodeValidator; import edu.ualberta.med.biobank.widgets.BiobankLabelProvider; import edu.ualberta.med.biobank.widgets.CancelConfirmWidget; import edu.ualberta.med.biobank.widgets.grids.cell.PalletCell; import edu.ualberta.med.biobank.widgets.grids.cell.UICellStatus; import edu.ualberta.med.scannerconfig.ScannerConfigPlugin; import edu.ualberta.med.scannerconfig.dmscanlib.ScanCell; import edu.ualberta.med.scannerconfig.preferences.scanner.profiles.ProfileManager; public abstract class AbstractPalletSpecimenAdminForm extends AbstractSpecimenAdminForm { private static final String PLATE_VALIDATOR = "plate-validator"; private BgcBaseText plateToScanText; protected Button scanButton; private String scanButtonTitle; protected CancelConfirmWidget cancelConfirmWidget; private static String plateToScanSessionString = ""; private IObservableValue plateToScanValue = new WritableValue( plateToScanSessionString, String.class); private IObservableValue canLaunchScanValue = new WritableValue( Boolean.TRUE, Boolean.class); private IObservableValue scanHasBeenLaunchedValue = new WritableValue( Boolean.FALSE, Boolean.class); private IObservableValue scanValidValue = new WritableValue(Boolean.TRUE, Boolean.class); private boolean rescanMode = false; private PalletScanManagement palletScanManagement; private Label scanTubeAloneSwitch; protected ComboViewer profilesCombo; private IPropertyChangeListener propertyListener; protected String currentPlateToScan; // global state of the pallet process protected UICellStatus currentScanState; private Label plateToScanLabel; @Override protected void init() throws Exception { super.init(); Assert.isNotNull(SessionManager.getUser().getCurrentWorkingCenter()); currentPlateToScan = plateToScanSessionString; addScannerPreferencesPropertyListener(); palletScanManagement = new PalletScanManagement() { @Override public boolean isScanTubeAloneMode() { // FIXME: see issue #1230. always activate this mode return true; } @Override protected void beforeThreadStart() { currentPlateToScan = plateToScanValue.getValue().toString(); AbstractPalletSpecimenAdminForm.this.beforeScanThreadStart(); } @Override protected void beforeScan() { setScanHasBeenLaunched(false, true); String msg = "----SCAN on plate {0}----"; if (isRescanMode()) { msg = "----RESCAN on plate {0}----"; } appendLog(NLS.bind(msg, currentPlateToScan)); } @Override protected Map<RowColPos, PalletCell> getFakeScanCells() throws Exception { return AbstractPalletSpecimenAdminForm.this.getFakeScanCells(); } @Override protected void processScanResult(IProgressMonitor monitor) throws Exception { AbstractPalletSpecimenAdminForm.this.processScanResult(monitor); } @Override protected void afterScanBeforeMerge() { setScanHasBeenLaunched(true, true); } @Override protected void afterSuccessfulScan() { appendLog(NLS .bind( "Scan completed - {0} specimens found", cells.keySet().size())); } @Override protected void afterScanAndProcess() { AbstractPalletSpecimenAdminForm.this.afterScanAndProcess(null); } @Override protected void scanAndProcessError(String errorMsg) { setScanValid(false); if (errorMsg != null && !errorMsg.isEmpty()) { appendLog(errorMsg); } } @Override protected void plateError() { setScanHasBeenLaunched(false, true); } @Override protected void postprocessScanTubeAlone(PalletCell cell) throws Exception { AbstractPalletSpecimenAdminForm.this .postprocessScanTubeAlone(cell); } @Override protected boolean canScanTubeAlone(PalletCell cell) { return AbstractPalletSpecimenAdminForm.this .canScanTubeAlone(cell); } }; } private void addScannerPreferencesPropertyListener() { propertyListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { // force a check on available plates String plateText = plateToScanText.getText(); plateToScanText.setText(""); plateToScanText.setText(plateText); } }; ScannerConfigPlugin.getDefault().getPreferenceStore() .addPropertyChangeListener(propertyListener); } protected void beforeScanThreadStart() { // default does nothing } @SuppressWarnings("unused") protected void afterScanAndProcess(Integer rowToProcess) { // default does nothing } @Override public void dispose() { ScannerConfigPlugin.getDefault().getPreferenceStore() .removePropertyChangeListener(propertyListener); super.dispose(); } @Override public boolean onClose() { synchronized (plateToScanSessionString) { plateToScanSessionString = (String) plateToScanValue.getValue(); if (finished || BiobankPlugin.getPlatesEnabledCount() != 1) { plateToScanSessionString = ""; } } return super.onClose(); } protected void setRescanMode() { if (palletScanManagement.getScansCount() > 0) { scanButton .setText("Retry scan"); rescanMode = true; enableFields(false); } } protected abstract void enableFields(boolean enable); protected void createScanButton(Composite parent) { scanButtonTitle = "Launch scan"; if (!BiobankPlugin.isRealScanEnabled()) { createFakeOptions(parent); scanButtonTitle = "Fake scan"; } scanButton = toolkit.createButton(parent, scanButtonTitle, SWT.PUSH); GridData gd = new GridData(); gd.widthHint = 100; scanButton.setLayoutData(gd); scanButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { launchScanAndProcessResult(); } }); scanButton.setEnabled(false); addBooleanBinding(new WritableValue(Boolean.FALSE, Boolean.class), canLaunchScanValue, "Errors have been previously detected. Cannot launch scan."); addBooleanBinding( new WritableValue(Boolean.FALSE, Boolean.class), scanHasBeenLaunchedValue, "Scanner should be launched"); addBooleanBinding(new WritableValue(Boolean.TRUE, Boolean.class), scanValidValue, "Errors in scanning result"); } protected void launchScanAndProcessResult() { palletScanManagement.launchScanAndProcessResult(plateToScanValue .getValue().toString(), getProfile(), isRescanMode()); refreshPalletDisplay(); } protected abstract void refreshPalletDisplay(); protected String getProfile() { if (profilesCombo == null || profilesCombo.getCombo().getItemCount() <= 0 || profilesCombo.getCombo().getSelectionIndex() < 0) return ProfileManager.ALL_PROFILE_NAME; return profilesCombo.getCombo().getItem( profilesCombo.getCombo().getSelectionIndex()); } protected void createProfileComboBox(Composite fieldsComposite) { Label lbl = widgetCreator.createLabel(fieldsComposite, "Profile"); profilesCombo = widgetCreator .createComboViewer( fieldsComposite, lbl, null, null, "Invalid profile selected", false, null, null, new BiobankLabelProvider()); GridData gd = new GridData(); gd.horizontalAlignment = SWT.FILL; gd.horizontalSpan = 2; profilesCombo.getCombo().setLayoutData(gd); loadProfileCombo(); } private void loadProfileCombo() { profilesCombo.getCombo().removeAll(); ArrayList<String> profileList = new ArrayList<String>(); for (String element : ProfileManager.instance().getProfiles().keySet()) { profileList.add(element); } Collections.sort(profileList); // Alphabetic sort for (String element : profileList) { profilesCombo.add(element); } profilesCombo.getCombo().select(0); } protected void createPlateToScanField(Composite fieldsComposite) { plateToScanLabel = widgetCreator.createLabel(fieldsComposite, "Plate to scan"); plateToScanText = (BgcBaseText) widgetCreator .createBoundWidget( fieldsComposite, BgcBaseText.class, SWT.NONE, plateToScanLabel, new String[0], plateToScanValue, new ScannerBarcodeValidator( "Enter a valid plate barcode"), PLATE_VALIDATOR); plateToScanText.addListener(SWT.DefaultSelection, new Listener() { @Override public void handleEvent(Event e) { if (scanButton.isEnabled()) { launchScanAndProcessResult(); } } }); plateToScanText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (scanButton != null) scanButton.setEnabled((Boolean) canLaunchScanValue .getValue() && fieldsValid()); } }); String tooltip = "No barcodes availables. See the preferences to complete the configuration."; List<String> barcodes = BiobankPlugin.getDefault() .getPossibleBarcodes(); if (barcodes.size() > 0) tooltip = "Available barcodes are: " + StringUtil.join(barcodes, ", "); plateToScanText.setToolTipText(tooltip); GridData gd = (GridData) plateToScanText.getLayoutData(); gd.horizontalAlignment = SWT.FILL; int parentNumColumns = ((GridLayout) fieldsComposite.getLayout()).numColumns; if (parentNumColumns > 2) gd.horizontalSpan = parentNumColumns - 1; plateToScanText.setLayoutData(gd); } protected void showPlateToScanField(boolean show) { widgetCreator.showWidget(plateToScanLabel, show); widgetCreator.showWidget(plateToScanText, show); widgetCreator.setBinding(PLATE_VALIDATOR, show && needPlate()); } @SuppressWarnings("unused") protected void createFakeOptions(Composite fieldsComposite) { // default does nothing } protected void createCancelConfirmWidget(Composite parent) { cancelConfirmWidget = new CancelConfirmWidget(parent, this, true); } protected Map<RowColPos, PalletCell> getFakeScanCells() throws Exception { return null; } @Override protected void handleStatusChanged(IStatus status) { if (status.getSeverity() == IStatus.OK) { setFormHeaderErrorMessage(getOkMessage(), IMessageProvider.NONE); cancelConfirmWidget.setConfirmEnabled(true); setConfirmEnabled(true); setDirty(true); } else { scanButton.setEnabled((Boolean) canLaunchScanValue.getValue() && fieldsValid()); setFormHeaderErrorMessage(status.getMessage(), IMessageProvider.ERROR); cancelConfirmWidget.setConfirmEnabled(false); setConfirmEnabled(false); } } protected abstract boolean fieldsValid(); protected void setScanValid(final boolean valid) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { scanValidValue.setValue(valid); } }); } protected boolean isScanValid() { return scanValidValue.getValue().equals(Boolean.TRUE); } protected void setScanHasBeenLaunched(boolean launched) { scanHasBeenLaunchedValue.setValue(launched); } protected boolean isScanHasBeenLaunched() { return scanHasBeenLaunchedValue.getValue().equals(true); } protected void setScanHasBeenLaunched(final boolean launched, boolean async) { if (async) Display.getDefault().asyncExec(new Runnable() { @Override public void run() { setScanHasBeenLaunched(launched); } }); else setScanHasBeenLaunched(launched); } protected boolean isRescanMode() { return rescanMode; } protected void removeRescanMode() { scanButton.setText(scanButtonTitle); rescanMode = false; } protected boolean isPlateValid() { return BiobankPlugin.getDefault().isValidPlateBarcode( plateToScanText.getText()); } protected void resetPlateToScan() { plateToScanText.setText(plateToScanSessionString); plateToScanValue.setValue(plateToScanSessionString); } protected void setBindings(boolean isSingleMode) { setScanHasBeenLaunched(isSingleMode); widgetCreator.setBinding(PLATE_VALIDATOR, !isSingleMode && needPlate()); } protected boolean needPlate() { return true; } protected void setCanLaunchScan(boolean canLauch) { canLaunchScanValue.setValue(canLauch); } protected void scanTubeAlone(MouseEvent e) { palletScanManagement.scanTubeAlone(e); } protected void postprocessScanTubeAlone(PalletCell palletCell) throws Exception { appendLog(NLS.bind( "Tube {0} scanned and set to position {1}", palletCell.getValue(), palletScanManagement.getContainerType().getPositionString( palletCell.getRowColPos()))); beforeScanTubeAlone(); CellProcessResult res = (CellProcessResult) SessionManager .getAppService() .doAction( getCellProcessAction(SessionManager.getUser() .getCurrentWorkingCenter() .getId(), palletCell.transformIntoServerCell(), Locale.getDefault())); palletCell.merge(SessionManager.getAppService(), res.getCell()); appendLogs(res.getLogs()); processCellResult(palletCell.getRowColPos(), palletCell); currentScanState = currentScanState.mergeWith(palletCell.getStatus()); setScanValid(getCells() != null && !getCells().isEmpty() && currentScanState != UICellStatus.ERROR); // boolean ok = isScanValid() // && (palletCell.getStatus() != UICellStatus.ERROR); // setScanValid(ok); afterScanAndProcess(palletCell.getRow()); } protected abstract Action<ProcessResult> getCellProcessAction( Integer centerId, CellInfo cell, Locale locale); protected abstract Action<ProcessResult> getPalletProcessAction( Integer centerId, Map<RowColPos, CellInfo> cells, boolean isRescanMode, Locale locale); protected void beforeScanTubeAlone() { // default does nothing } protected boolean isScanTubeAloneMode() { return palletScanManagement.isScanTubeAloneMode(); } protected void toggleScanTubeAloneMode() { palletScanManagement.toggleScanTubeAloneMode(); } protected void createScanTubeAloneButton(Composite parent) { scanTubeAloneSwitch = toolkit.createLabel(parent, "", SWT.NONE); GridData gd = new GridData(); gd.verticalAlignment = SWT.TOP; scanTubeAloneSwitch.setLayoutData(gd); scanTubeAloneSwitch.setImage(BgcPlugin.getDefault().getImageRegistry() .get(BgcPlugin.IMG_SCAN_EDIT)); scanTubeAloneSwitch.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { if (isScanHasBeenLaunched()) { palletScanManagement.toggleScanTubeAloneMode(); if (palletScanManagement.isScanTubeAloneMode()) { scanTubeAloneSwitch.setImage(BgcPlugin.getDefault() .getImageRegistry() .get(BgcPlugin.IMG_SCAN_CLOSE_EDIT)); } else { scanTubeAloneSwitch.setImage(BgcPlugin.getDefault() .getImageRegistry().get(BgcPlugin.IMG_SCAN_EDIT)); } } } }); // FIXME: see issue #1230. deactivate this button until the users say we // can really remove it scanTubeAloneSwitch.setVisible(false); } @SuppressWarnings("unused") protected void showScanTubeAloneSwitch(boolean show) { // FIXME: see issue #1230. deactivate this button until the users say we // can really remove it // widgetCreator.showWidget(scanTubeAloneSwitch, show); } protected Map<RowColPos, PalletCell> getCells() { return palletScanManagement.getCells(); } @Override public void setValues() throws Exception { scanValidValue.setValue(true); palletScanManagement.onReset(); enableFields(true); resetPlateToScan(); } protected void setUseScanner(boolean useScanner) { palletScanManagement.setUseScanner(useScanner); if (useScanner) currentScanState = null; else currentScanState = UICellStatus.EMPTY; } /** * go through cells retrieved from scan, set status and update the types * combos components */ protected void processScanResult(IProgressMonitor monitor) throws Exception { Map<RowColPos, PalletCell> cells = getCells(); // conversion for server side call Map<RowColPos, edu.ualberta.med.biobank.common.action.scanprocess.CellInfo> serverCells = null; if (cells != null) { serverCells = new HashMap<RowColPos, edu.ualberta.med.biobank.common.action.scanprocess.CellInfo>(); for (Entry<RowColPos, PalletCell> entry : cells.entrySet()) { serverCells.put(entry.getKey(), entry.getValue() .transformIntoServerCell()); } } // server side call ScanProcessResult res = (ScanProcessResult) SessionManager .getAppService().doAction( getPalletProcessAction(SessionManager.getUser() .getCurrentWorkingCenter().getId(), serverCells, isRescanMode(), Locale.getDefault())); // print result logs appendLogs(res.getLogs()); if (cells != null) { // for each cell, convert into a client side cell for (Entry<RowColPos, edu.ualberta.med.biobank.common.action.scanprocess.CellInfo> entry : res .getCells().entrySet()) { RowColPos rcp = entry.getKey(); monitor .subTask(NLS .bind( "Processing position {0}", palletScanManagement.getContainerType() .getPositionString(rcp))); PalletCell palletCell = cells.get(entry.getKey()); CellInfo servercell = entry.getValue(); if (palletCell == null) { // can happened if missing palletCell = new PalletCell(new ScanCell( servercell.getRow(), servercell.getCol(), servercell.getValue())); cells.put(rcp, palletCell); } palletCell.merge(SessionManager.getAppService(), servercell); // additional cell specific client conversion if needed processCellResult(rcp, palletCell); } } currentScanState = UICellStatus.valueOf(res.getProcessStatus().name()); setScanValid(getCells() != null && !getCells().isEmpty() && currentScanState != UICellStatus.ERROR); } @SuppressWarnings("unused") protected void processCellResult(RowColPos rcp, PalletCell palletCell) { // nothing done by default } protected boolean isAtLeastOneScanLaunched() { return palletScanManagement.getScansCount() > 0; } protected boolean canScanTubeAlone(PalletCell cell) { return cell == null || cell.getStatus() == UICellStatus.EMPTY; } protected void focusControl(final Control control) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { control.setFocus(); } }); } protected void focusPlateToScan() { focusControl(plateToScanText); } protected void initCellsWithContainer( ContainerWrapper currentMultipleContainer) { if (currentMultipleContainer != null) { palletScanManagement .initCellsWithContainer(currentMultipleContainer); } } protected void setContainerType(ContainerType type) { palletScanManagement.setContainerType(type); } }