package org.openlca.app.results.grouping; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; 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.Shell; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.openlca.app.M; import org.openlca.app.db.Database; import org.openlca.app.rcp.images.Icon; import org.openlca.app.rcp.images.Images; import org.openlca.app.util.Actions; import org.openlca.app.util.Labels; import org.openlca.app.util.Question; import org.openlca.app.util.UI; import org.openlca.app.util.tables.Tables; import org.openlca.app.util.viewers.Viewers; import org.openlca.core.model.FlowType; import org.openlca.core.model.ProcessGroup; import org.openlca.core.model.ProcessGroupSet; import org.openlca.core.model.descriptors.ProcessDescriptor; import org.openlca.core.results.ContributionResultProvider; import org.openlca.core.results.ProcessGrouping; import org.openlca.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The page of the analysis editor with the grouping function. */ public class GroupPage extends FormPage { List<ProcessGrouping> groups; ProcessGroupSet groupSet; ContributionResultProvider<?> result; private TableViewer groupViewer; private TableViewer processViewer; private Menu groupMoveMenu; private GroupResultSection resultSection; private Section groupingSection; public GroupPage(FormEditor editor, ContributionResultProvider<?> result) { super(editor, "analysis.GroupPage", M.Grouping); this.result = result; initGroups(result); } private void initGroups(ContributionResultProvider<?> result) { groups = new ArrayList<>(); ProcessGrouping restGroup = new ProcessGrouping(); restGroup.name = M.Other; restGroup.rest = true; for (ProcessDescriptor p : result.getProcessDescriptors()) restGroup.processes.add(p); groups.add(restGroup); } public void applyGrouping(ProcessGroupSet groupSet) { if (groupSet == null || result == null) return; this.groupSet = groupSet; List<ProcessDescriptor> processes = new ArrayList<>(); for (ProcessDescriptor p : result.getProcessDescriptors()) processes.add(p); List<ProcessGrouping> newGroups = ProcessGrouping.applyOn(processes, groupSet, M.Other); groups.clear(); groups.addAll(newGroups); updateViewers(); updateTitle(); } private void updateViewers() { if (groupViewer != null && processViewer != null && resultSection != null) { groupViewer.refresh(true); processViewer.setInput(Collections.emptyList()); resultSection.update(); } } /** * Add the current group set name to the section title, if it is not null. */ void updateTitle() { if (groupingSection == null) return; if (groupSet == null) groupingSection.setText(M.Groups); else groupingSection.setText(M.Groups + " (" + groupSet.getName() + ")"); } @Override protected void createFormContent(IManagedForm managedForm) { ScrolledForm form = UI.formHeader(managedForm, M.Grouping); FormToolkit toolkit = managedForm.getToolkit(); Composite body = UI.formBody(form, toolkit); createGroupingSection(toolkit, body); resultSection = new GroupResultSection(groups, result); resultSection.render(body, toolkit); form.reflow(true); } private void createGroupingSection(FormToolkit toolkit, Composite body) { groupingSection = UI.section(body, toolkit, M.Groups); Composite composite = UI.sectionClient(groupingSection, toolkit); UI.gridLayout(composite, 2); Actions.bind(groupingSection, new AddGroupAction(), new SaveGroupSetAction(this), new GroupSetAction(this)); createGroupViewer(composite); processViewer = Tables.createViewer(composite); UI.gridData(processViewer.getControl(), true, false).heightHint = 200; configureViewer(processViewer); createMoveMenu(); } private void createGroupViewer(Composite composite) { groupViewer = Tables.createViewer(composite); GridData groupData = UI.gridData(groupViewer.getControl(), false, false); groupData.heightHint = 200; groupData.widthHint = 250; configureViewer(groupViewer); groupViewer.setInput(groups); Actions.bind(groupViewer, new DeleteGroupAction()); groupViewer.addSelectionChangedListener(e -> { ProcessGrouping g = Viewers.getFirst(e.getSelection()); if (g != null) processViewer.setInput(g.processes); }); } private void configureViewer(TableViewer viewer) { viewer.setLabelProvider(new GroupPageLabel()); viewer.setContentProvider(new ArrayContentProvider()); viewer.setSorter(new GroupPageSorter()); } private void createMoveMenu() { Menu menu = new Menu(processViewer.getTable()); processViewer.getTable().setMenu(menu); MenuItem item = new MenuItem(menu, SWT.CASCADE); item.setText(M.Move); groupMoveMenu = new Menu(item); item.setMenu(groupMoveMenu); groupMoveMenu.addListener(SWT.Show, new MenuGroupListener()); } private class AddGroupAction extends Action { public AddGroupAction() { setImageDescriptor(Icon.ADD.descriptor()); setToolTipText(M.Add); } @Override public void run() { String m = M.PleaseEnterAName; InputDialog dialog = new InputDialog(getSite().getShell(), m, m, "", null); int code = dialog.open(); if (code == Window.OK) { String name = dialog.getValue(); ProcessGrouping group = new ProcessGrouping(); group.name = name; groups.add(group); groupViewer.add(group); resultSection.update(); } } } private class DeleteGroupAction extends Action { public DeleteGroupAction() { setImageDescriptor(Icon.DELETE.descriptor()); setText(M.Delete); } @Override public void run() { ProcessGrouping grouping = Viewers.getFirstSelected(groupViewer); if (grouping == null || grouping.rest) return; ProcessGrouping rest = findRest(); if (rest == null) return; groups.remove(grouping); rest.processes.addAll(grouping.processes); updateViewers(); } private ProcessGrouping findRest() { if (groups == null) return null; for (ProcessGrouping g : groups) { if (g.rest) return g; } return null; } } private class MenuGroupListener implements Listener, SelectionListener, Comparator<ProcessGrouping> { @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } /** * Executed when an item is selected: moves processes to a group. */ @Override public void widgetSelected(SelectionEvent e) { Object o = e.widget.getData(); if (!(o instanceof ProcessGrouping)) return; ProcessGrouping targetGroup = (ProcessGrouping) o; ProcessGrouping sourceGroup = Viewers.getFirstSelected(groupViewer); if (sourceGroup == null) return; List<ProcessDescriptor> processes = Viewers .getAllSelected(processViewer); if (processes == null || processes.isEmpty()) return; move(sourceGroup, targetGroup, processes); } private void move(ProcessGrouping sourceGroup, ProcessGrouping targetGroup, List<ProcessDescriptor> processes) { sourceGroup.processes.removeAll(processes); targetGroup.processes.addAll(processes); processViewer.setInput(sourceGroup.processes); resultSection.update(); } /** * Executed when the menu is shown: fills the group-menu */ @Override public void handleEvent(Event event) { ProcessGrouping group = Viewers.getFirstSelected(groupViewer); if (group == null) return; for (MenuItem item : groupMoveMenu.getItems()) { item.removeSelectionListener(this); item.dispose(); } List<ProcessGrouping> other = getOther(group); for (ProcessGrouping g : other) { MenuItem menuItem = new MenuItem(groupMoveMenu, SWT.PUSH); menuItem.setText(g.name); menuItem.setData(g); menuItem.addSelectionListener(this); } } private List<ProcessGrouping> getOther(ProcessGrouping group) { List<ProcessGrouping> other = new ArrayList<>(); for (ProcessGrouping g : groups) { if (g.equals(group)) continue; other.add(g); } Collections.sort(other, this); return other; } @Override public int compare(ProcessGrouping o1, ProcessGrouping o2) { if (o1 == null || o2 == null) return 0; return Strings.compare(o1.name, o2.name); } } private class GroupPageLabel extends LabelProvider { @Override public Image getImage(Object element) { if (element instanceof ProcessGrouping) return Icon.FOLDER_BLUE.get(); return Images.get(FlowType.PRODUCT_FLOW); } @Override public String getText(Object element) { if (element instanceof ProcessGrouping) { ProcessGrouping group = (ProcessGrouping) element; return group.name; } else if (element instanceof ProcessDescriptor) { ProcessDescriptor p = (ProcessDescriptor) element; return Labels.getDisplayName(p); } else return null; } } /** * A viewer sorter for groups and processes on the grouping page. */ private class GroupPageSorter extends ViewerSorter { @Override public int compare(Viewer viewer, Object first, Object second) { if ((first instanceof ProcessGrouping) && (second instanceof ProcessGrouping)) return compareGroups((ProcessGrouping) first, (ProcessGrouping) second); if ((first instanceof ProcessDescriptor) && (second instanceof ProcessDescriptor)) return compareProcesses((ProcessDescriptor) first, (ProcessDescriptor) second); return 0; } private int compareProcesses(ProcessDescriptor first, ProcessDescriptor second) { return compareNames(first.getName(), second.getName()); } private int compareGroups(ProcessGrouping first, ProcessGrouping second) { return compareNames(first.name, second.name); } private int compareNames(String first, String second) { if (first == null) return -1; if (second == null) return 1; return first.compareToIgnoreCase(second); } } /** * Action for saving a group set in the grouping page of the analysis * editor. */ private class SaveGroupSetAction extends Action { private Logger log = LoggerFactory.getLogger(getClass()); private GroupPage page; public SaveGroupSetAction(GroupPage page) { this.page = page; setToolTipText(M.Save); setImageDescriptor(Icon.SAVE.descriptor()); } @Override public void run() { ProcessGroupSet groupSet = page.groupSet; if (groupSet == null) insertNew(); else updateExisting(groupSet); } private void insertNew() { ProcessGroupSet groupSet; try { groupSet = createGroupSet(); if (groupSet == null) return; setAndSaveGroups(groupSet); page.groupSet = groupSet; page.updateTitle(); } catch (Exception e) { log.error("Failed to save process group set", e); } } private ProcessGroupSet createGroupSet() throws Exception { Shell shell = page.getEditorSite().getShell(); InputDialog dialog = new InputDialog(shell, M.SaveAs, M.PleaseEnterAName, "", null); int code = dialog.open(); if (code == Window.CANCEL) return null; ProcessGroupSet set = new ProcessGroupSet(); set.setName(dialog.getValue()); Database.createDao(ProcessGroupSet.class).insert(set); return set; } private void updateExisting(ProcessGroupSet groupSet) { try { boolean b = Question.ask(M.SaveChanges, M.SaveChangesQuestion); if (b) setAndSaveGroups(groupSet); } catch (Exception e) { log.error("Failed to save process group set", e); } } private List<ProcessGroup> createGroups() { List<ProcessGrouping> pageGroups = page.groups; if (pageGroups == null) return Collections.emptyList(); List<ProcessGroup> groups = new ArrayList<>(); for (ProcessGrouping pageGroup : pageGroups) { if (pageGroup.rest) continue; ProcessGroup group = new ProcessGroup(); group.setName(pageGroup.name); groups.add(group); for (ProcessDescriptor process : pageGroup.processes) group.getProcessIds().add(process.getRefId()); } return groups; } private void setAndSaveGroups(ProcessGroupSet groupSet) throws Exception { List<ProcessGroup> groups = createGroups(); groupSet.setGroups(groups); Database.createDao(ProcessGroupSet.class).update(groupSet); page.groupSet = groupSet; } } }