package edu.ualberta.med.biobank.forms; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; 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.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.widgets.Section; import edu.ualberta.med.biobank.SessionManager; import edu.ualberta.med.biobank.common.reports.filters.FilterOperator; import edu.ualberta.med.biobank.common.util.AbstractBiobankListProxy; import edu.ualberta.med.biobank.common.util.ReportListProxy; import edu.ualberta.med.biobank.common.wrappers.ModelWrapper; import edu.ualberta.med.biobank.common.wrappers.ReportWrapper; import edu.ualberta.med.biobank.common.wrappers.util.WrapperUtil; import edu.ualberta.med.biobank.export.CsvDataExporter; import edu.ualberta.med.biobank.export.Data; import edu.ualberta.med.biobank.export.DataExporter; import edu.ualberta.med.biobank.export.PdfDataExporter; import edu.ualberta.med.biobank.export.PrintPdfDataExporter; import edu.ualberta.med.biobank.forms.listener.ProgressMonitorDialogBusyListener; import edu.ualberta.med.biobank.gui.common.BgcLogger; import edu.ualberta.med.biobank.gui.common.BgcPlugin; import edu.ualberta.med.biobank.gui.common.validators.NonEmptyStringValidator; import edu.ualberta.med.biobank.gui.common.widgets.BgcBaseText; import edu.ualberta.med.biobank.model.EntityFilter; import edu.ualberta.med.biobank.model.Log; import edu.ualberta.med.biobank.model.Report; import edu.ualberta.med.biobank.model.ReportColumn; import edu.ualberta.med.biobank.model.ReportFilter; import edu.ualberta.med.biobank.model.ReportFilterValue; import edu.ualberta.med.biobank.treeview.AdapterBase; import edu.ualberta.med.biobank.treeview.report.ReportAdapter; import edu.ualberta.med.biobank.views.AdvancedReportsView; import edu.ualberta.med.biobank.widgets.infotables.ReportResultsTableWidget; import edu.ualberta.med.biobank.widgets.report.ChangeListener; import edu.ualberta.med.biobank.widgets.report.ColumnChangeEvent; import edu.ualberta.med.biobank.widgets.report.ColumnSelectWidget; import edu.ualberta.med.biobank.widgets.report.FilterChangeEvent; import edu.ualberta.med.biobank.widgets.report.FilterSelectWidget; public class ReportEntryForm extends BiobankEntryForm { private static BgcLogger logger = BgcLogger.getLogger(ReportEntryForm.class .getName()); private static ImageDescriptor SAVE_AS_NEW_ACTION_IMAGE = ImageDescriptor .createFromImage(BgcPlugin.getDefault().getImageRegistry() .get(BgcPlugin.IMG_SAVE_AS_NEW)); public static final String ID = "edu.ualberta.med.biobank.forms.ReportEntryForm"; private static final Comparator<EntityFilter> COMPARE_FILTERS_BY_NAME = new Comparator<EntityFilter>() { @Override public int compare(EntityFilter lhs, EntityFilter rhs) { return lhs.getName().compareToIgnoreCase(rhs.getName()); } }; private FilterSelectWidget filtersWidget; private ColumnSelectWidget columnsWidget; private ReportAdapter reportAdapter; private ReportWrapper report; private ComboViewer filterCombo; private Section filtersSection; private Button generateButton; private final Collection<Button> exportButtons = new ArrayList<Button>(); private Composite resultsContainer; private List<Object> results; private ReportResultsTableWidget<Object> resultsTable; private PrintPdfDataExporter printPdfDataExporter; @Override protected void init() throws Exception { reportAdapter = (ReportAdapter) adapter; report = (ReportWrapper) getModelObject(); report.reload(); updatePartName(); } @Override protected void doAfterSave() { AdvancedReportsView.getCurrent().reload(); } @Override protected void saveForm() throws Exception { form.getDisplay().syncExec(new Runnable() { @Override public void run() { // update the model before saving updateReportModel(); } }); report.persist(); reportAdapter.getParent().performExpand(); } private void updateReportModel() { // don't set through the wrappers because we don't want to alert // anything listening to the wrapper (for example, the // FilterSelectWidget and the ColumnSelectWidget). Report nakedReport = report.getWrappedObject(); nakedReport.setReportColumns(new HashSet<ReportColumn>( columnsWidget.getReportColumns())); nakedReport.setReportFilters(new HashSet<ReportFilter>( filtersWidget.getReportFilters())); } @Override protected String getOkMessage() { return ""; } @Override public String getNextOpenedFormId() { return ReportEntryForm.ID; } private void updatePartName() { String entityName = report.getEntity().getName(); String tabName; if (report.isNew()) { tabName = NLS.bind("New {0} Report", entityName); report.setName(tabName); } else { String reportName = report.getName(); if (reportName == null || reportName.isEmpty()) { tabName = NLS.bind("Unnamed {0} Report", entityName); } else { tabName = reportName; } } setPartName(tabName); } @Override protected void createFormContent() throws Exception { form.setText(NLS.bind("{0} Report", report .getEntity().getName())); page.setLayout(new GridLayout(1, false)); createProperties(); createFiltersSection(); createOptionsSection(); createButtons(); createResultsArea(); } private void createResultsArea() { resultsContainer = toolkit.createComposite(page); GridLayout layout = new GridLayout(1, false); resultsContainer.setLayout(layout); resultsContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } private void createProperties() { Composite container = toolkit.createComposite(page); GridLayout layout = new GridLayout(2, false); layout.horizontalSpacing = 10; container.setLayout(layout); container.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); toolkit.paintBordersFor(container); setFirstControl(createBoundWidgetWithLabel(container, BgcBaseText.class, SWT.NONE, "Name", null, report, ReportWrapper.PROPERTY_NAME, new NonEmptyStringValidator( "Name is required."))); createBoundWidgetWithLabel(container, BgcBaseText.class, SWT.MULTI, "Description", null, report, ReportWrapper.PROPERTY_DESCRIPTION, null); } private void createButtons() { Composite container = toolkit.createComposite(page); GridLayout layout = new GridLayout(2, false); layout.horizontalSpacing = 10; container.setLayout(layout); container.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); toolkit.paintBordersFor(container); createGenerateButton(container); createExportButtons(container).setLayoutData( new GridData(SWT.END, SWT.TOP, true, false)); } private Control createGenerateButton(Composite parent) { generateButton = toolkit.createButton(parent, "Generate", SWT.NONE); generateButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { generateReport(); } }); updateGenerateButton(); return generateButton; } private void emptyResultsContainer() { for (Control control : resultsContainer.getChildren()) { if (!control.isDisposed()) { control.dispose(); } } resultsTable = null; } private void generateReport() { for (Button button : exportButtons) { button.setEnabled(false); } updateReportModel(); emptyResultsContainer(); final Report rawReport = report.getWrappedObject(); IRunnableWithProgress op = new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) { monitor.beginTask("Generating report...", IProgressMonitor.UNKNOWN); try { results = new ArrayList<Object>(); Thread thread = new Thread( "Querying") { @SuppressWarnings("unchecked") @Override public void run() { results = (List<Object>) new ReportListProxy( SessionManager.getAppService(), rawReport) .init(); if (results instanceof AbstractBiobankListProxy) ((AbstractBiobankListProxy<?>) results) .addBusyListener(new ProgressMonitorDialogBusyListener( "Loading more results...")); } }; thread.start(); while (true) { if (monitor.isCanceled()) { thread.interrupt(); return; } else if (!thread.isAlive()) { break; } try { Thread.sleep(500); } catch (InterruptedException e) { return; } } generateButton.getDisplay().syncExec(new Runnable() { @Override public void run() { resultsTable = new ReportResultsTableWidget<Object>( resultsContainer, null, getHeaders()); resultsTable.adaptToToolkit(toolkit, true); resultsTable.setList(results); if (!report.getIsCount()) { resultsTable .addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick( DoubleClickEvent event) { ISelection selection = event .getSelection(); openViewForm(selection); } }); } for (Button button : exportButtons) { button.setEnabled(true); } } }); Log logMessage = new Log(); logMessage.setType("report"); logMessage.setAction(rawReport.getName()); SessionManager.getAppService().logActivity(logMessage); addPrintAction(); } catch (Exception e) { BgcPlugin.openAsyncError( "Report Generation Error", e); } monitor.done(); } }; try { new ProgressMonitorDialog(PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getShell()).run(true, true, op); } catch (Exception e) { throw new RuntimeException(e); } finally { book.reflow(true); form.layout(true, true); } } private Control createExportButtons(Composite parent) { Composite container = toolkit.createComposite(parent); GridLayout layout = new GridLayout(3, false); layout.horizontalSpacing = 10; container.setLayout(layout); container.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); toolkit.paintBordersFor(container); createExporterButton(container, new CsvDataExporter()); createExporterButton(container, new PdfDataExporter()); printPdfDataExporter = new PrintPdfDataExporter(); Button printButton = createExporterButton(container, printPdfDataExporter); printButton.setImage(BgcPlugin.getDefault().getImageRegistry() .get(BgcPlugin.IMG_PRINTER)); return container; } private Button createExporterButton(Composite parent, final DataExporter exporter) { Button button = toolkit.createButton(parent, exporter.getName(), SWT.NONE); button.setEnabled(false); button.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { export(exporter); } }); exportButtons.add(button); return button; } @Override public boolean print() { export(printPdfDataExporter); return true; } private void openViewForm(ISelection selection) { if (selection instanceof IStructuredSelection) { Object o = ((IStructuredSelection) selection).getFirstElement(); if (o instanceof Object[]) { Object[] row = (Object[]) o; if (row.length > 0 && row[0] instanceof Integer) { Integer id = (Integer) row[0]; String entityClassName = report.getEntity().getClassName(); try { Class<?> entityKlazz = Class.forName(entityClassName); Constructor<?> constructor = entityKlazz .getConstructor(); Object instance = constructor.newInstance(); Method setIdMethod = entityKlazz.getMethod("setId", Integer.class); setIdMethod.invoke(instance, id); ModelWrapper<?> wrapper = WrapperUtil.wrapObject( SessionManager.getAppService(), instance); SessionManager.openViewForm(wrapper); } catch (Exception e) { logger.error( "Error opening selection", e); } } } } } private String[] getHeaders() { Report nakedReport = report.getWrappedObject(); List<ReportColumn> reportColumns = new ArrayList<ReportColumn>( nakedReport.getReportColumns()); Collections.sort(reportColumns, new Comparator<ReportColumn>() { @Override public int compare(ReportColumn lhs, ReportColumn rhs) { return lhs.getPosition() - rhs.getPosition(); } }); int numHeaders = reportColumns.size(); numHeaders += report.getIsCount() ? 1 : 0; String[] headers = new String[numHeaders]; int i = 0; for (ReportColumn reportColumn : reportColumns) { headers[i] = ColumnSelectWidget.getColumnName(reportColumn); i++; } if (report.getIsCount()) { headers[i] = NLS.bind("{0} Count", report.getEntity().getName()); } return headers; } private void createFiltersSection() { filtersSection = createSection("Filters"); Composite container = toolkit.createComposite(filtersSection, SWT.NONE); GridLayout layout = new GridLayout(1, false); layout.horizontalSpacing = 0; layout.verticalSpacing = 0; container.setLayout(layout); container.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); toolkit.paintBordersFor(container); createFilterCombo(container); filtersWidget = new FilterSelectWidget(container, SWT.NONE, report); filtersWidget .addFilterChangedListener(new ChangeListener<FilterChangeEvent>() { @Override public void handleEvent(FilterChangeEvent event) { if (event.isDataChange()) { setDirty(true); } book.reflow(true); form.layout(true, true); EntityFilter entityFilter = event.getEntityFilter(); if (event.isSelected()) { filterCombo.remove(entityFilter); } else { filterCombo.add(entityFilter); } } }); Collection<EntityFilter> entityFilters = getSortedEntityFilters(report, COMPARE_FILTERS_BY_NAME); for (EntityFilter entityFilter : entityFilters) { if (filtersWidget.getFilterRow(entityFilter) == null) { filterCombo.add(entityFilter); } } filtersSection.setClient(container); } private void createFilterCombo(Composite parent) { Composite container = toolkit.createComposite(parent); GridLayout layout = new GridLayout(2, false); layout.horizontalSpacing = 5; layout.verticalSpacing = 0; layout.marginWidth = 0; layout.marginHeight = 2; container.setLayout(layout); GridData layoutData = new GridData(); layoutData.verticalAlignment = SWT.TOP; layoutData.horizontalAlignment = SWT.RIGHT; container.setLayoutData(layoutData); Label label = new Label(container, SWT.NONE); label.setText("Add filter:"); GridData comboLayoutData = new GridData(); comboLayoutData.widthHint = 200; filterCombo = new ComboViewer(container, SWT.READ_ONLY); filterCombo.getControl().setLayoutData(comboLayoutData); filterCombo.setSorter(new ViewerSorter()); filterCombo.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { if (element instanceof EntityFilter) { return ((EntityFilter) element).getName(); } return ""; } }); filterCombo .addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { Object selection = ((IStructuredSelection) filterCombo .getSelection()).getFirstElement(); if (selection instanceof EntityFilter) { EntityFilter entityFilter = (EntityFilter) selection; filterCombo.remove(entityFilter); filtersWidget.addFilterRow(entityFilter); setDirty(true); form.layout(true, true); } } }); } private static Collection<EntityFilter> getSortedEntityFilters( ReportWrapper report, Comparator<EntityFilter> comparator) { List<EntityFilter> sortedFilters = new ArrayList<EntityFilter>(); sortedFilters.addAll(report.getEntityFilterCollection()); Collections.sort(sortedFilters, comparator); return sortedFilters; } private void createOptionsSection() { Section section = createSection("Options"); Composite options = toolkit.createComposite(section); GridLayout layout = new GridLayout(2, false); layout.horizontalSpacing = 10; options.setLayout(layout); options.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); toolkit.paintBordersFor(options); createBoundWidgetWithLabel(options, Button.class, SWT.CHECK, "Show count\r\n(for displayed columns)", null, report, "isCount", null); createBoundWidgetWithLabel(options, Button.class, SWT.CHECK, "Share report", null, report, "isPublic", null); GridData layoutData = new GridData(); layoutData.widthHint = 225; Label columnsLabel = new Label(options, SWT.NONE); columnsLabel.setText("Columns:"); columnsLabel.setLayoutData(layoutData); columnsWidget = new ColumnSelectWidget(options, SWT.NONE, report); columnsWidget .addColumnChangeListener(new ChangeListener<ColumnChangeEvent>() { @Override public void handleEvent(ColumnChangeEvent event) { setDirty(true); book.reflow(true); form.layout(true, true); updateGenerateButton(); } }); section.setClient(options); } private void updateGenerateButton() { boolean hasColumnsSelected = !columnsWidget.getReportColumns() .isEmpty(); if (generateButton != null && !generateButton.isDisposed()) { generateButton.setEnabled(hasColumnsSelected); } } @Override protected void addToolbarButtons() { super.addToolbarButtons(); Action saveAsNewAction = new Action() { @Override public void run() { updateReportModel(); ReportWrapper report = new ReportWrapper( ReportEntryForm.this.report); closeEntryOpenView(false, false); int userId = SessionManager.getUser().getId().intValue(); report.setUserId(userId); ReportAdapter reportAdapter = new ReportAdapter( (AdapterBase) getAdapter().getParent(), report); IEditorPart part = reportAdapter.openEntryForm(); if (part instanceof ReportEntryForm) { ReportEntryForm form = (ReportEntryForm) part; form.setDirty(true); form.confirm(); } } }; // TODO: Need to add a command (and handler) for a shortcut to work, but // it won't be able to invoke the Action's run method. // saveAsNewAction // .setActionDefinitionId("edu.ualberta.med.biobank.commands.saveAsNew"); saveAsNewAction.setImageDescriptor(SAVE_AS_NEW_ACTION_IMAGE); saveAsNewAction.setToolTipText("Save As New Report"); form.getToolBarManager().add(saveAsNewAction); form.updateToolBar(); } private void export(final DataExporter exporter) { final Data data = new Data(); data.setColumnNames(Arrays.asList(getHeaders())); data.setTitle(report.getName()); data.setDescription(getComments(report)); data.setRows(results); // check if the exporter can export this data try { exporter.canExport(data); } catch (Exception e) { MessageDialog.openError(PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getShell(), "Confirm Report Results Export", e.getMessage()); return; } // confirm exporting if (!MessageDialog.openQuestion( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Confirm Report Results Export", NLS.bind("Are you sure you want to {0}?", exporter.getName()))) { return; } // export try { exporter.export(data, resultsTable .getLabelProvider(!(exporter instanceof CsvDataExporter))); } catch (Exception e) { MessageDialog.openError(PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getShell(), "Error exporting", e.getMessage()); return; } } private List<String> getComments(ReportWrapper report) { List<String> comments = new ArrayList<String>(); if (report.getDescription() != null) { comments.add(report.getDescription()); } Report nakedReport = report.getWrappedObject(); List<ReportFilter> reportFilters = new ArrayList<ReportFilter>( nakedReport.getReportFilters()); Collections.sort(reportFilters, new Comparator<ReportFilter>() { @Override public int compare(ReportFilter lhs, ReportFilter rhs) { return lhs.getPosition() - rhs.getPosition(); } }); for (ReportFilter filter : reportFilters) { StringBuilder sb = new StringBuilder(); sb.append(filter.getEntityFilter().getName()); Integer opId = filter.getOperator(); if (opId != null) { FilterOperator op = FilterOperator.getFilterOperator(opId); if (op != null) { sb.append(" "); sb.append(op.getDisplayString()); } } Collection<ReportFilterValue> values = filter .getReportFilterValues(); if (values != null) { sb.append(": "); for (ReportFilterValue value : values) { sb.append("'"); sb.append(value.getValue().replace("'", "\'")); if (value.getSecondValue() != null) { sb.append("' and '"); sb.append(value.getSecondValue().replace("'", "\'")); } sb.append("'; "); } } comments.add(sb.toString()); } return comments; } @Override public void setValues() throws Exception { emptyResultsContainer(); report.reset(); } }