/** * Copyright (C) Intersect 2012. * * This module contains Proprietary Information of Intersect, * and should be treated as Confidential. */ package au.org.intersect.exsite9.view; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IExecutionListener; import org.eclipse.core.commands.NotHandledException; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ExpandEvent; import org.eclipse.swt.events.ExpandListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; 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.layout.GridLayout; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.ExpandBar; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.part.ViewPart; import com.google.common.base.Objects; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import au.org.intersect.exsite9.Activator; import au.org.intersect.exsite9.domain.Group; import au.org.intersect.exsite9.domain.IMetadataAssignable; import au.org.intersect.exsite9.domain.MetadataAttribute; import au.org.intersect.exsite9.domain.MetadataAttributeValue; import au.org.intersect.exsite9.domain.MetadataCategory; import au.org.intersect.exsite9.domain.MetadataCategoryType; import au.org.intersect.exsite9.domain.MetadataValue; import au.org.intersect.exsite9.domain.NewFilesGroup; import au.org.intersect.exsite9.domain.Project; import au.org.intersect.exsite9.domain.ResearchFile; import au.org.intersect.exsite9.domain.RootGroup; import au.org.intersect.exsite9.domain.utils.AlphabeticalMetadataAttributeValueComparator; import au.org.intersect.exsite9.domain.utils.AlphabeticalMetadataValueComparator; import au.org.intersect.exsite9.domain.utils.IDMetadataCategoryComparator; import au.org.intersect.exsite9.domain.utils.MetadataAssignableUtils; import au.org.intersect.exsite9.domain.utils.MetadataAssociationTripletComparator; import au.org.intersect.exsite9.service.IGroupService; import au.org.intersect.exsite9.service.IMetadataCategoryService; import au.org.intersect.exsite9.service.IMetadataCategoryViewConfigService; import au.org.intersect.exsite9.service.IProjectManager; import au.org.intersect.exsite9.service.IResearchFileService; import au.org.intersect.exsite9.util.Triplet; import au.org.intersect.exsite9.validators.MetadataValueValidator; import au.org.intersect.exsite9.view.listener.MetadataCategorySelectionListener; import au.org.intersect.exsite9.view.widgets.MetadataAttributeValuesComboWidget; import au.org.intersect.exsite9.view.widgets.MetadataButtonWidget; import au.org.intersect.exsite9.view.widgets.MetadataCategoryExpandItem; import au.org.intersect.exsite9.view.widgets.MetadataTextWidget; /** * View component used for browsing Metadata. */ public final class MetadataBrowserView extends ViewPart implements IExecutionListener, SelectionListener, ISelectionListener { public static final String ID = MetadataBrowserView.class.getName(); private static final Logger LOG = Logger.getLogger(MetadataBrowserView.class); private static final int RIGHT_MARGIN = 40; private ExpandBar expandBar; private Composite parent; private Composite placeholder; private final List<RowData> rows = new ArrayList<RowData>(); private final IGroupService groupService; private final IMetadataCategoryService metadataCategoryService; private final IResearchFileService researchFileService; private final IMetadataCategoryViewConfigService metadataCategoryViewConfigService; /** * The list of currently selected (if any) groups on the RHS. */ private final List<IMetadataAssignable> selectedMetadataAssignables = new ArrayList<IMetadataAssignable>(); /** * The metadata buttons that are currently shown on the page - keyed by metadata category for easy lookup. */ private final Map<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>, MetadataButtonWidget> metadataButtons = new HashMap<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>, MetadataButtonWidget>(); private final Map<MetadataCategory, Composite> freeTextParentComposites = new HashMap<MetadataCategory, Composite>(); private final Map<MetadataCategory, List<FreeTextRowComposite>> freeTextRows = new HashMap<MetadataCategory, List<FreeTextRowComposite>>(); /** * */ public MetadataBrowserView() { this.groupService = (IGroupService) PlatformUI.getWorkbench().getService(IGroupService.class); this.researchFileService = (IResearchFileService) PlatformUI.getWorkbench().getService(IResearchFileService.class); this.metadataCategoryService = (IMetadataCategoryService) PlatformUI.getWorkbench().getService(IMetadataCategoryService.class); this.metadataCategoryViewConfigService = (IMetadataCategoryViewConfigService) PlatformUI.getWorkbench().getService(IMetadataCategoryViewConfigService.class); } /** * @{inheritDoc */ @Override public void createPartControl(final Composite parent) { final ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService( ICommandService.class); final Command newProjectCommand = commandService .getCommand("au.org.intersect.exsite9.commands.NewProjectCommand"); newProjectCommand.addExecutionListener(this); final Command openProjectCommand = commandService .getCommand("au.org.intersect.exsite9.commands.OpenProjectCommand"); openProjectCommand.addExecutionListener(this); final Command addMetadataCategoryCommand = commandService .getCommand("au.org.intersect.exsite9.commands.AddMetadataCategoryCommand"); addMetadataCategoryCommand.addExecutionListener(this); final Command editProjectCommand = commandService.getCommand("au.org.intersect.exsite9.commands.EditProjectCommand"); editProjectCommand.addExecutionListener(this); final Command importMetadataSchemaCommand = commandService.getCommand("au.org.intersect.exsite9.commands.ImportMetadataSchemaCommand"); importMetadataSchemaCommand.addExecutionListener(this); final Command removeMetadataCategoryCommand = commandService.getCommand("au.org.intersect.exsite9.commands.RemoveMetadataCategoryCommand"); removeMetadataCategoryCommand.addExecutionListener(this); final Command editMetadataCategoryCommand = commandService.getCommand("au.org.intersect.exsite9.commands.EditMetadataCategoryCommand"); editMetadataCategoryCommand.addExecutionListener(this); final Command removeMetadataCategoryWithWizardCommand = commandService.getCommand("au.org.intersect.exsite9.commands.RemoveMetadataCategoryWithWizardCommand"); removeMetadataCategoryWithWizardCommand.addExecutionListener(this); this.parent = parent; // So wrapping of the buttons in the rows will work this.parent.addControlListener(new ControlAdapter() { @Override public void controlResized(final ControlEvent e) { super.controlResized(e); for (final RowData rowData : rows) { // -50 to allow for the scrollbar - this is a bug with SWT. rowData.width = parent.getClientArea().width - RIGHT_MARGIN; } } }); // So we can listen for selections in the project tree view getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(ProjectExplorerView.ID, this); } private void initLayout(final List<MetadataCategory> metadataCategories) { if (this.expandBar != null) { this.expandBar.dispose(); } if (this.placeholder != null) { this.placeholder.dispose(); } this.metadataButtons.clear(); this.freeTextParentComposites.clear(); this.freeTextRows.clear(); this.rows.clear(); if (metadataCategories.isEmpty()) { this.placeholder = new Composite(this.parent, SWT.BORDER); return; } this.expandBar = new ExpandBar(this.parent, SWT.BORDER | SWT.V_SCROLL); this.expandBar.addExpandListener(new ExpandListener() { @Override public void itemExpanded(final ExpandEvent e) { final MetadataCategoryExpandItem expandItem = (MetadataCategoryExpandItem) e.item; final MetadataCategory metadataCategory = expandItem.getMetadataCategory(); metadataCategoryViewConfigService.setExpanded(metadataCategory, true); } @Override public void itemCollapsed(final ExpandEvent e) { final MetadataCategoryExpandItem expandItem = (MetadataCategoryExpandItem) e.item; final MetadataCategory metadataCategory = expandItem.getMetadataCategory(); metadataCategoryViewConfigService.setExpanded(metadataCategory, false); } }); final List<MetadataCategory> sorted = new ArrayList<MetadataCategory>(metadataCategories); Collections.sort(sorted, new IDMetadataCategoryComparator()); for (final MetadataCategory metadataCategory : sorted) { final Composite expandBarComposite = new Composite(expandBar, SWT.NONE); final RowLayout expandBarLayout = new RowLayout(SWT.VERTICAL); expandBarLayout.wrap = true; expandBarLayout.pack = true; expandBarLayout.justify = false; expandBarComposite.setLayout(expandBarLayout); final Composite headerComposite = new Composite(expandBarComposite, SWT.NONE); final MetadataCategoryExpandItem expandItem = new MetadataCategoryExpandItem(this.expandBar, SWT.NONE, metadataCategory); expandItem.setText(metadataCategory.getName() + " (" + metadataCategory.getUse() + ")"); expandItem.setControl(expandBarComposite); expandItem.setHeight(expandBarComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y); expandBarComposite.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { super.controlResized(e); expandItem.setHeight(expandBarComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y); } }); final ToolBar toolBar = new ToolBar(headerComposite, SWT.FLAT | SWT.WRAP | SWT.RIGHT); new ToolItem(toolBar, SWT.SEPARATOR); final ToolItem editButtonItem = new ToolItem(toolBar, SWT.NULL); final Image editImage = Activator.getImageDescriptor("/icons/icon_edit_16.png").createImage(); editButtonItem.setImage(editImage); editButtonItem.addSelectionListener(new MetadataCategorySelectionListener(metadataCategory, "au.org.intersect.exsite9.commands.AddMetadataCategoryCommand", "au.org.intersect.exsite9.commands.AddMetadataCategoryCommand.categoryParameter")); editButtonItem.setToolTipText("Edit category"); final ToolItem removeButtonItem = new ToolItem(toolBar, SWT.NULL); final Image removeImage = Activator.getImageDescriptor("/icons/icon_exclude_16.png").createImage(); removeButtonItem.setImage(removeImage); removeButtonItem.setToolTipText("Delete category"); removeButtonItem.addSelectionListener(new MetadataCategorySelectionListener(metadataCategory, "au.org.intersect.exsite9.commands.RemoveMetadataCategoryCommand", "au.org.intersect.exsite9.commands.RemoveMetadataCategoryCommand.categoryParameter")); if((metadataCategory.getDescription() != null) && (!metadataCategory.getDescription().isEmpty())){ final ToolItem helpButtonItem = new ToolItem(toolBar, SWT.FLAT); final Image helpImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_LCL_LINKTO_HELP); helpButtonItem.setImage(helpImage); helpButtonItem.setToolTipText(metadataCategory.getDescription()); } new ToolItem(toolBar, SWT.SEPARATOR); toolBar.pack(); if (metadataCategory.getType() == MetadataCategoryType.CONTROLLED_VOCABULARY) { final Composite contentComposite = new Composite(expandBarComposite, SWT.NONE); layoutRow(contentComposite); final RowLayout buttonLayout = new RowLayout(SWT.HORIZONTAL); buttonLayout.marginLeft = 10; buttonLayout.marginRight = 10; buttonLayout.marginTop = 10; buttonLayout.marginBottom = 10; buttonLayout.wrap = true; buttonLayout.pack = true; buttonLayout.justify = false; contentComposite.setLayout(buttonLayout); // Sort the metadata values. final List<MetadataValue> sortedMetadataValues = new ArrayList<MetadataValue>(metadataCategory.getValues()); Collections.sort(sortedMetadataValues, new AlphabeticalMetadataValueComparator()); for (final MetadataValue metadataValue : sortedMetadataValues) { final MetadataButtonWidget mdbw = new MetadataButtonWidget(contentComposite, SWT.TOGGLE, metadataCategory, metadataValue); mdbw.setText(metadataValue.getValue()); mdbw.addSelectionListener(this); final Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue> triplet = new Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>(metadataCategory, metadataValue, null); this.metadataButtons.put(triplet, mdbw); } } else { freeTextParentComposites.put(metadataCategory, expandBarComposite); } final boolean expanded = metadataCategoryViewConfigService.isExpanded(metadataCategory); expandItem.setExpanded(expanded); } packAndLayout(); } private void layoutRow(final Composite contentComposite) { final RowData rowData = new RowData(); // -50 to allow for the scrollbar - this is a bug with SWT. rowData.width = parent.getClientArea().width - RIGHT_MARGIN; contentComposite.setLayoutData(rowData); rows.add(rowData); } private void packAndLayout() { if (this.expandBar != null) { expandBar.pack(); expandBar.layout(); } parent.layout(); } /** * @{inheritDoc */ @Override public void setFocus() { } @Override public void notHandled(final String commandId, final NotHandledException exception) { } @Override public void postExecuteFailure(final String commandId, final ExecutionException exception) { } @Override public void postExecuteSuccess(final String commandId, final Object returnValue) { if (commandId.equals("au.org.intersect.exsite9.commands.NewProjectCommand") || commandId.equals("au.org.intersect.exsite9.commands.OpenProjectCommand") || commandId.equals("au.org.intersect.exsite9.commands.AddMetadataCategoryCommand") || commandId.equals("au.org.intersect.exsite9.commands.EditProjectCommand") || commandId.equals("au.org.intersect.exsite9.commands.ImportMetadataSchemaCommand") || commandId.equals("au.org.intersect.exsite9.commands.RemoveMetadataCategoryCommand") || commandId.equals("au.org.intersect.exsite9.commands.EditMetadataCategoryCommand") || commandId.equals("au.org.intersect.exsite9.commands.RemoveMetadataCategoryWithWizardCommand")) { reloadView(); } } private void reloadView() { final IProjectManager projectManager = (IProjectManager) PlatformUI.getWorkbench().getService(IProjectManager.class); final Project project = projectManager.getCurrentProject(); if (project != null) { initLayout(project.getSchema().getMetadataCategories()); if (!project.getSchema().getMetadataCategories().isEmpty()) { // Refresh this page according to the currently selected items in the RHS. final ProjectExplorerView projectExplorerView = (ProjectExplorerView) ViewUtils.getViewByID( PlatformUI.getWorkbench().getActiveWorkbenchWindow(), ProjectExplorerView.ID); projectExplorerView.refresh(); final ISelection currentSelection = projectExplorerView.getSelection(); selectionChanged(projectExplorerView, currentSelection); } } } @Override public void preExecute(final String commandId, final ExecutionEvent event) { } @Override public void widgetSelected(final SelectionEvent e) { final MetadataButtonWidget button = (MetadataButtonWidget) e.widget; if (!validMetadataAssignablesSelected(true)) { button.setSelection(false); return; } final MetadataCategory metadataCategory = button.getMetadataCategory(); final MetadataValue metadataValue = button.getMetadataValue(); if (this.selectedMetadataAssignables.size() > 1) { final boolean performOperation = MessageDialog.openConfirm(getSite().getWorkbenchWindow().getShell(), "Caution","The metadata operation is about to be performed on all selected items. Are you sure you wish to proceed?"); if (!performOperation) { button.setSelection(!button.getSelection()); return; } } if (button.getSelection()) { for (final IMetadataAssignable metadataAssignable : this.selectedMetadataAssignables) { if (metadataAssignable instanceof Group) { // We need to reload the object model from the database because the user may have updated it's metadata associations // and wishes to perform another action on it (without selecting another one in between, which would force a reload from the DB) final Group freshGroup = this.groupService.findGroupByID(((Group)metadataAssignable).getId()); groupService.associateMetadata(freshGroup, metadataCategory, metadataValue, null); } else if (metadataAssignable instanceof ResearchFile) { final ResearchFile freshResearchFile = this.researchFileService.findResearchFileByID(((ResearchFile)metadataAssignable).getId()); researchFileService.associateMetadata(freshResearchFile, metadataCategory, metadataValue, null); } } } else { for (final IMetadataAssignable metadataAssignable : this.selectedMetadataAssignables) { if (metadataAssignable instanceof Group) { final Group freshGroup = this.groupService.findGroupByID(((Group)metadataAssignable).getId()); groupService.disassociateMetadata(freshGroup, metadataCategory, metadataValue); } else if (metadataAssignable instanceof ResearchFile) { final ResearchFile freshResearchFile = this.researchFileService.findResearchFileByID(((ResearchFile)metadataAssignable).getId()); researchFileService.disassociateMetadata(freshResearchFile, metadataCategory, metadataValue); } } } refreshRelatedViews(); } /** * Ensures the user has selected valid metadata assignables in the project explorer view. * @return {@code true} if selection is valid, {@code false} otherwise. */ private boolean validMetadataAssignablesSelected(final boolean showErrors) { if (this.selectedMetadataAssignables.isEmpty()) { if (showErrors) { MessageDialog.openError(getSite().getWorkbenchWindow().getShell(), "Error", "Please select an item to assign metadata with."); } return false; } // Check that the selected groups does not contain a new files group OR the project node - we CANNOT assign metadata // to them. if (!Collections2.filter(this.selectedMetadataAssignables, Predicates.instanceOf(NewFilesGroup.class)).isEmpty()) { if (showErrors) { MessageDialog.openError(getSite().getWorkbenchWindow().getShell(), "Error", "Metadata cannot be assigned to the new files group."); } return false; } else if (!Collections2.filter(this.selectedMetadataAssignables, Predicates.instanceOf(RootGroup.class)).isEmpty()) { if (showErrors) { MessageDialog.openError(getSite().getWorkbenchWindow().getShell(), "Error", "Metadata cannot be assigned to a project."); } return false; } return true; } /** * Refreshes related views - performed after some action is performed on this view that other views may be interested in. */ private static void refreshRelatedViews() { // Refresh the table in the Associated Metadata View when a value button is selected or de-selected final AssociatedMetadataView associatedMetadataView = (AssociatedMetadataView) ViewUtils.getViewByID( PlatformUI.getWorkbench().getActiveWorkbenchWindow(), AssociatedMetadataView.ID); associatedMetadataView.refresh(); // Refresh the Project Explorer View according to the currently selected items in the RHS. final ProjectExplorerView projectExplorerView = (ProjectExplorerView) ViewUtils.getViewByID( PlatformUI.getWorkbench().getActiveWorkbenchWindow(), ProjectExplorerView.ID); projectExplorerView.refresh(); } @Override public void widgetDefaultSelected(final SelectionEvent e) { } /** * Listens to selection changes on the tree viewer in the project view on the LHS. * * @{inheritDoc */ @Override public void selectionChanged(final IWorkbenchPart part, final ISelection selection) { if (!(selection instanceof IStructuredSelection)) { LOG.error("Unknown selection type"); return; } final IStructuredSelection structuredSelection = (IStructuredSelection) selection; @SuppressWarnings("unchecked") final List<Object> selectedObjects = structuredSelection.toList(); this.selectedMetadataAssignables.clear(); for (final Object selectedObject : selectedObjects) { if (selectedObject instanceof Group && !(selectedObject instanceof NewFilesGroup)) { final Group selectedGroup = this.groupService.findGroupByID(((Group) selectedObject).getId()); this.selectedMetadataAssignables.add(selectedGroup); } else if (selectedObject instanceof ResearchFile) { final ResearchFile selectedResearchFile = this.researchFileService.findResearchFileByID(((ResearchFile) selectedObject).getId()); this.selectedMetadataAssignables.add(selectedResearchFile); } } resetMetadataValueWidgets(); if (!this.selectedMetadataAssignables.isEmpty()) { // Determine a common set of buttons that should be pressed and press them. final Set<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>> intersection = new HashSet<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>>( MetadataAssignableUtils.getCategoryToValueMapping(this.selectedMetadataAssignables.get(0))); for (int i = 1; i < this.selectedMetadataAssignables.size(); i++) { intersection.retainAll(MetadataAssignableUtils.getCategoryToValueMapping(this.selectedMetadataAssignables.get(i))); } setMetadataValueWidgets(new ArrayList<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>>(intersection)); } else { for (final MetadataCategory mdc : this.freeTextParentComposites.keySet()) { if (!this.freeTextRows.containsKey(mdc)) { final Composite composite = freeTextParentComposites.get(mdc); final FreeTextRowComposite row = new FreeTextRowComposite(composite, SWT.NULL, mdc, null, null, true); this.freeTextRows.put(mdc, new ArrayList<MetadataBrowserView.FreeTextRowComposite>(Arrays.asList(row))); layoutRow(row.getComposite()); } } packAndLayout(); } } /** * Used to enable/disable this view from other views. * @param enabled {@code true} to enable this view. */ public void setEnabled(final boolean enabled) { this.parent.setEnabled(enabled); this.parent.setVisible(enabled); } /** * Sets the metadata values that are selected. * @param metadataCollectionTrio the metadata to select. */ private void setMetadataValueWidgets(final List<Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue>> metadataCollectionTrio) { Collections.sort(metadataCollectionTrio, new MetadataAssociationTripletComparator()); for (final Triplet<MetadataCategory, MetadataValue, MetadataAttributeValue> triplet : metadataCollectionTrio) { final MetadataCategory metadataCategory = triplet.getFirst(); if (metadataCategory.getType() == MetadataCategoryType.CONTROLLED_VOCABULARY) { final MetadataButtonWidget metadataButtonWidget = this.metadataButtons.get(triplet); if (metadataButtonWidget != null) { metadataButtonWidget.setSelection(true); } } else { final Composite composite = freeTextParentComposites.get(metadataCategory); final MetadataValue metadataValue = triplet.getSecond(); final MetadataAttributeValue metadataAttributeValue = triplet.getThird(); final List<FreeTextRowComposite> rowsForMetadataCategory; if (this.freeTextRows.containsKey(metadataCategory)) { rowsForMetadataCategory = this.freeTextRows.get(metadataCategory); } else { rowsForMetadataCategory = new ArrayList<FreeTextRowComposite>(); this.freeTextRows.put(metadataCategory, rowsForMetadataCategory); } final FreeTextRowComposite row = new FreeTextRowComposite(composite, SWT.NULL, metadataCategory, metadataValue, metadataAttributeValue, rowsForMetadataCategory.isEmpty()); rowsForMetadataCategory.add(row); layoutRow(row.getComposite()); } } for (final MetadataCategory mdc : this.freeTextParentComposites.keySet()) { if (!this.freeTextRows.containsKey(mdc)) { final Composite composite = freeTextParentComposites.get(mdc); final FreeTextRowComposite row = new FreeTextRowComposite(composite, SWT.NULL, mdc, null, null, true); this.freeTextRows.put(mdc, new ArrayList<MetadataBrowserView.FreeTextRowComposite>(Arrays.asList(row))); layoutRow(row.getComposite()); } } packAndLayout(); } /** * Depresses (i.e. makes them NOT clicked) all the metadata value buttons currently on the page. */ private void resetMetadataValueWidgets() { for (final MetadataButtonWidget metadataButtonWidget : this.metadataButtons.values()) { metadataButtonWidget.setSelection(false); } for (final List<FreeTextRowComposite> freeTextRows : this.freeTextRows.values()) { for (final FreeTextRowComposite freeTextRow : freeTextRows ) { freeTextRow.getComposite().dispose(); } } this.freeTextRows.clear(); packAndLayout(); } /** * Represents a row used for Free Text Metadata Categories. */ public final class FreeTextRowComposite { private final Composite contentComposite; private final MetadataCategory metadataCategory; private final MetadataValue metadataValue; private final MetadataAttributeValue metadataAttributeValue; private final boolean first; public FreeTextRowComposite(final Composite parent, final int style, final MetadataCategory mdc, final MetadataValue metadataValue, final MetadataAttributeValue metadataAttribtueValue, final boolean first) { this.contentComposite = new Composite(parent, style); this.metadataCategory = mdc; this.metadataValue = metadataValue; this.metadataAttributeValue = metadataAttribtueValue; this.first = first; init(); } public Composite getComposite() { return contentComposite; } private void init() { final MetadataAttribute metadataAttribute = metadataCategory.getMetadataAttribute(); final boolean hasAttributes = metadataAttribute != null; final int numCols = hasAttributes ? 7 : 5; final GridLayout gridLayout = new GridLayout(numCols, false); contentComposite.setLayout(gridLayout); final MetadataAttributeValuesComboWidget combo; if (hasAttributes) { final Label attributeNameLabel = new Label(contentComposite, SWT.NULL); attributeNameLabel.setText(metadataAttribute.getName() + ":"); combo = new MetadataAttributeValuesComboWidget(contentComposite, SWT.READ_ONLY); final List<MetadataAttributeValue> values = new ArrayList<MetadataAttributeValue>(metadataAttribute.getMetadataAttributeValues()); Collections.sort(values, new AlphabeticalMetadataAttributeValueComparator()); combo.setItems(values); } else { combo = null; } final MetadataTextWidget freeTextField = new MetadataTextWidget(contentComposite, SWT.BORDER | SWT.SINGLE); freeTextField.setText(metadataValue == null ? "" : metadataValue.getValue()); freeTextField.setMetadataValue(metadataValue); freeTextField.setToolTipText(freeTextField.getText()); if (combo != null && metadataAttributeValue != null) { combo.select(metadataAttributeValue); combo.setMetadataAttributeValue(metadataAttributeValue); } final GridData fillGridLayout = new GridData(GridData.FILL_HORIZONTAL); freeTextField.setLayoutData(fillGridLayout); final Button resetButton = new Button(contentComposite, SWT.PUSH); resetButton.setText("Reset"); resetButton.setEnabled(false); final Button applyButton = new Button(contentComposite, SWT.PUSH); applyButton.setText("Apply"); applyButton.setEnabled(false); final Button minusButton = new Button(contentComposite, SWT.PUSH); minusButton.setText("-"); minusButton.setEnabled(!first); final Button plusButton = new Button(contentComposite, SWT.PUSH); plusButton.setText("+"); if (combo != null) { combo.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(final SelectionEvent e) { if (!validMetadataAssignablesSelected(false)) { resetButton.setEnabled(false); applyButton.setEnabled(false); return; } final MetadataAttributeValue currentValue = combo.getSelectedMetadataAttributeValue(); final boolean differentAttributeValue = !Objects.equal(metadataAttributeValue, currentValue); final String originalText = metadataValue == null ? "" : metadataValue.getValue(); final String newText = freeTextField.getText().trim(); final boolean differentTextValue = !Objects.equal(newText, originalText); freeTextField.setToolTipText(newText); resetButton.setEnabled(differentAttributeValue || differentTextValue); applyButton.setEnabled((differentAttributeValue && !newText.isEmpty()) || (differentTextValue && !newText.isEmpty())); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); } resetButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(final SelectionEvent e) { final String originalText = metadataValue == null ? "" : metadataValue.getValue(); freeTextField.setText(originalText); freeTextField.setSelection(originalText.length(), originalText.length()); freeTextField.setToolTipText(freeTextField.getText()); if (combo != null) { if (metadataAttributeValue != null) { combo.select(metadataAttributeValue); } else { combo.select(0); } } resetButton.setEnabled(false); applyButton.setEnabled(false); } @Override public void widgetDefaultSelected(final SelectionEvent e) { } }); applyButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(final SelectionEvent event) { if (!validMetadataAssignablesSelected(true)) { return; } // Handle the assignment. if (selectedMetadataAssignables.size() > 1) { final boolean performOperation = MessageDialog.openConfirm(getSite().getWorkbenchWindow().getShell(), "Caution","The metadata operation is about to be performed on all selected items. Are you sure you wish to proceed?"); if (!performOperation) { return; } } final String newValue = freeTextField.getText(); final MetadataAttributeValue metadataAttributeValue = combo != null ? combo.getSelectedMetadataAttributeValue() : null; final MetadataValueValidator validtor = new MetadataValueValidator(Collections.<MetadataValue>emptyList(), true); if (!validtor.isValid(newValue)) { MessageDialog.openError(getSite().getWorkbenchWindow().getShell(), "Cannot apply metadata", "Cannot apply metadata. " + validtor.getErrorMessage()); return; } // We need to reload the metadata category. final MetadataCategory updatedMetadataCategory = metadataCategoryService.findById(metadataCategory.getId()); // Disassociate old value final MetadataValue oldAssociatedValue = freeTextField.getMetadataValue(); if (oldAssociatedValue != null) { disassociateFreeText(updatedMetadataCategory, oldAssociatedValue); } // Add & Associate new value if (!newValue.isEmpty()) { // Persist the newly configured metadata category value. final MetadataValue metadataValue = metadataCategoryService.addValueToMetadataCategory(updatedMetadataCategory, newValue); for (final IMetadataAssignable metadataAssignable : selectedMetadataAssignables) { if (metadataAssignable instanceof Group) { // We need to reload the object model from the database because the user may have updated it's metadata associations // and wishes to perform another action on it (without selecting another one in between, which would force a reload from the DB) final Group freshGroup = groupService.findGroupByID(((Group)metadataAssignable).getId()); groupService.associateMetadata(freshGroup, updatedMetadataCategory, metadataValue, metadataAttributeValue); } else if (metadataAssignable instanceof ResearchFile) { final ResearchFile freshResearchFile = researchFileService.findResearchFileByID(((ResearchFile)metadataAssignable).getId()); researchFileService.associateMetadata(freshResearchFile, updatedMetadataCategory, metadataValue, metadataAttributeValue); } } freeTextField.setMetadataValue(metadataValue); applyButton.setEnabled(false); resetButton.setEnabled(false); } refreshRelatedViews(); } @Override public void widgetDefaultSelected(final SelectionEvent event) { } }); plusButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(final SelectionEvent e) { final Composite composite = freeTextParentComposites.get(metadataCategory); final FreeTextRowComposite row = new FreeTextRowComposite(composite, SWT.NULL, metadataCategory, null, null, false); freeTextRows.get(metadataCategory).add(row); layoutRow(row.getComposite()); final Composite parent = contentComposite.getParent(); parent.pack(); parent.layout(); } @Override public void widgetDefaultSelected(final SelectionEvent e) { } }); minusButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(final SelectionEvent e) { // Remove metadata category/value mapping - which includes disassocation. final MetadataCategory updatedMetadataCategory = metadataCategoryService.findById(metadataCategory.getId()); final MetadataValue oldAssociatedValue = freeTextField.getMetadataValue(); if (oldAssociatedValue != null) { disassociateFreeText(updatedMetadataCategory, oldAssociatedValue); } final Composite parent = contentComposite.getParent(); contentComposite.dispose(); parent.pack(); parent.layout(); refreshRelatedViews(); } @Override public void widgetDefaultSelected(final SelectionEvent e) { } }); freeTextField.addKeyListener(new KeyListener() { @Override public void keyReleased(final KeyEvent e) { if (!validMetadataAssignablesSelected(false)) { resetButton.setEnabled(false); applyButton.setEnabled(false); return; } final String originalText = metadataValue == null ? "" : metadataValue.getValue(); final String newText = freeTextField.getText().trim(); final boolean enabled = !originalText.equals(newText); freeTextField.setToolTipText(newText); resetButton.setEnabled(enabled); applyButton.setEnabled(enabled && !newText.isEmpty()); } @Override public void keyPressed(final KeyEvent e) { } }); } private void disassociateFreeText(final MetadataCategory updatedMetadataCategory, final MetadataValue oldAssociatedValue) { for (final IMetadataAssignable metadataAssignable : selectedMetadataAssignables) { if (metadataAssignable instanceof Group) { // We need to reload the object model from the database because the user may have updated it's metadata associations // and wishes to perform another action on it (without selecting another one in between, which would force a reload from the DB) final Group freshGroup = groupService.findGroupByID(((Group)metadataAssignable).getId()); groupService.disassociateMetadata(freshGroup, updatedMetadataCategory, oldAssociatedValue); } else if (metadataAssignable instanceof ResearchFile) { final ResearchFile freshResearchFile = researchFileService.findResearchFileByID(((ResearchFile)metadataAssignable).getId()); researchFileService.disassociateMetadata(freshResearchFile, updatedMetadataCategory, oldAssociatedValue); } } } } }