ref #8146 Restrict character matrix to characters
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / descriptiveDataSet / DescriptiveDataSetEditor.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.editor.descriptiveDataSet;
10
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.UUID;
20 import java.util.stream.Collectors;
21
22 import javax.annotation.PostConstruct;
23 import javax.annotation.PreDestroy;
24 import javax.inject.Inject;
25 import javax.inject.Named;
26
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.e4.core.contexts.ContextInjectionFactory;
29 import org.eclipse.e4.core.contexts.IEclipseContext;
30 import org.eclipse.e4.ui.di.Focus;
31 import org.eclipse.e4.ui.di.Persist;
32 import org.eclipse.e4.ui.model.application.ui.MDirtyable;
33 import org.eclipse.e4.ui.services.EMenuService;
34 import org.eclipse.e4.ui.services.IServiceConstants;
35 import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
36 import org.eclipse.jface.util.LocalSelectionTransfer;
37 import org.eclipse.jface.viewers.IStructuredSelection;
38 import org.eclipse.jface.viewers.StructuredSelection;
39 import org.eclipse.jface.viewers.Viewer;
40 import org.eclipse.jface.window.Window;
41 import org.eclipse.swt.SWT;
42 import org.eclipse.swt.dnd.DND;
43 import org.eclipse.swt.dnd.Transfer;
44 import org.eclipse.swt.events.FocusAdapter;
45 import org.eclipse.swt.events.FocusEvent;
46 import org.eclipse.swt.events.KeyAdapter;
47 import org.eclipse.swt.events.KeyEvent;
48 import org.eclipse.swt.events.ModifyEvent;
49 import org.eclipse.swt.events.ModifyListener;
50 import org.eclipse.swt.events.SelectionAdapter;
51 import org.eclipse.swt.widgets.Composite;
52 import org.eclipse.swt.widgets.Display;
53 import org.eclipse.swt.widgets.Shell;
54
55 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
56 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
57 import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService;
58 import eu.etaxonomy.cdm.api.service.ITaxonNodeService;
59 import eu.etaxonomy.cdm.api.service.ITermService;
60 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
61 import eu.etaxonomy.cdm.model.common.TermType;
62 import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
63 import eu.etaxonomy.cdm.model.description.FeatureTree;
64 import eu.etaxonomy.cdm.model.location.NamedArea;
65 import eu.etaxonomy.cdm.model.name.Rank;
66 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
67 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
68 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
69 import eu.etaxonomy.cdm.persistence.dto.TermDto;
70 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
71 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
72 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
73 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
74 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
75 import eu.etaxonomy.taxeditor.model.MessagingUtils;
76 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
77 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
78 import eu.etaxonomy.taxeditor.store.CdmStore;
79 import eu.etaxonomy.taxeditor.ui.element.CdmFormFactory;
80 import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
81
82 /**
83 * Editor for configuring DescriptiveDataSets
84 * @author pplitzner
85 * @since Nov 21, 2017
86 *
87 */
88 public class DescriptiveDataSetEditor implements IE4SavablePart, IConversationEnabled, ICdmEntitySessionEnabled,
89 IPartContentHasDetails, IPartContentHasSupplementalData, IDirtyMarkable {
90
91 private DescriptiveDataSetComposite composite;
92
93 private ConversationHolder conversation;
94
95 private ICdmEntitySession cdmEntitySession;
96
97 private DescriptiveDataSet descriptiveDataSet;
98
99 private final int dndOperations = DND.DROP_MOVE;
100
101 @Inject
102 private MDirtyable dirty;
103
104 @Inject
105 private ESelectionService selectionService;
106
107 private ModifyListener labelModifyListener;
108
109 @PostConstruct
110 public void create(Composite parent, IEclipseContext context, @Named(IServiceConstants.ACTIVE_SHELL)Shell shell,
111 EMenuService menuService){
112 if (CdmStore.isActive()){
113 if(conversation == null){
114 conversation = CdmStore.createConversation();
115 }
116 if(cdmEntitySession == null){
117 cdmEntitySession = CdmStore.getCurrentSessionManager().newSession(this, true);
118 }
119 }
120 else{
121 return;
122 }
123 CdmFormFactory cdmFormFactory = new CdmFormFactory(Display.getCurrent());
124 ContextInjectionFactory.inject(cdmFormFactory, context);
125
126 composite = new DescriptiveDataSetComposite(parent, SWT.NONE);
127
128 labelModifyListener = new ModifyListener() {
129 @Override
130 public void modifyText(ModifyEvent e) {
131 descriptiveDataSet.setLabel(composite.getTxt_label().getText());
132 dirty.setDirty(true);
133 }
134 };
135 composite.getRankMin().addSelectionChangedListener(event->dirty.setDirty(true));
136 composite.getRankMin().setText("Select min rank...");
137 composite.getRankMax().addSelectionChangedListener(event->dirty.setDirty(true));
138 composite.getRankMin().setText("Select max rank...");
139 composite.getBtnRemoveRankMin().addSelectionListener(new SelectionAdapter() {
140 @Override
141 public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
142 composite.getRankMin().setElement(null);
143 dirty.setDirty(true);
144 }
145 });
146 composite.getBtnRemoveRankMax().addSelectionListener(new SelectionAdapter() {
147 @Override
148 public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
149 composite.getRankMax().setElement(null);
150 dirty.setDirty(true);
151 }
152 });
153
154 composite.getBtnChooseArea().addSelectionListener(new SelectionAdapter() {
155
156 @Override
157 public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
158 AreasSelectionDialog areasSelectionDialog = new AreasSelectionDialog(composite.getShell(), composite.getAreas());
159 if(areasSelectionDialog.open()==Window.OK){
160 List<TermDto> selectedAreas = areasSelectionDialog.getSelectedAreas();
161 if(selectedAreas!=null){
162 composite.setAreas(selectedAreas);
163 dirty.setDirty(true);
164 }
165 }
166 }
167 });
168
169 composite.getFeatureTreeEditorComposite().init("Character Tree",
170 TermType.Character,
171 null,
172 null,
173 e->selectionService.setSelection(e.getSelection()),
174 this,
175 null);
176
177 //add drag'n'drop support
178 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer()};
179 composite.getTaxonNodeTree().addDropSupport(dndOperations, transfers, new TaxonNodeDropAdapter(this));
180
181 composite.getTaxonNodeTree().getTree().addKeyListener(new KeyAdapter() {
182 @Override
183 public void keyPressed(KeyEvent e) {
184 if(e.character==SWT.DEL){
185 removeSelectedTaxonNodes();
186 }
187 }
188 });
189
190
191 //create context menu
192 menuService.registerContextMenu(composite.getTaxonNodeTree().getControl(), "eu.etaxonomy.taxeditor.editor.descriptiveDataSet.DescriptiveDataSetEditor.popupmenu.specimeneditor");
193
194 }
195
196 public void init(UUID descriptiveDataSetUuid) {
197 this.descriptiveDataSet = cdmEntitySession.remoteLoad(CdmStore.getService(IDescriptiveDataSetService.class), descriptiveDataSetUuid);
198 if(descriptiveDataSet.getLabel()!=null){
199 composite.getTxt_label().setText(descriptiveDataSet.getLabel());
200 }
201 if(descriptiveDataSet.getDescriptiveSystem()!=null){
202 composite.setCharacters(descriptiveDataSet.getDescriptiveSystem());
203 }
204 Rank maxRank = descriptiveDataSet.getMaxRank();
205 if(maxRank!=null){
206 composite.setRankMax(maxRank);
207 }
208 Rank minRank = descriptiveDataSet.getMinRank();
209 if(minRank!=null){
210 composite.setRankMin(minRank);
211 }
212 Set<NamedArea> geoFilter = descriptiveDataSet.getGeoFilter();
213 if(geoFilter!=null && !geoFilter.isEmpty()){
214 Set<TermDto> terms = geoFilter.stream().map(filter->TermDto.fromTerm(filter, true)).collect(Collectors.toSet());
215 composite.setAreas(new ArrayList<>(terms));
216 }
217 Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
218 if(taxonSubtreeFilter!=null){
219 composite.getTaxonNodeTree().setInput(taxonSubtreeFilter);
220 }
221 composite.getTxt_label().addModifyListener(labelModifyListener);
222 composite.getTxt_label().addFocusListener(new FocusAdapter() {
223 @Override
224 public void focusGained(FocusEvent e) {
225 super.focusGained(e);
226 selectionService.setSelection(new StructuredSelection(descriptiveDataSet));
227 }
228 });
229 selectionService.setSelection(new StructuredSelection(descriptiveDataSet));
230 }
231
232 public void addTaxonNode(TaxonNodeDto taxonNodeDto){
233 TaxonNode taxonNode = cdmEntitySession.remoteLoad(CdmStore.getService(ITaxonNodeService.class), taxonNodeDto.getUuid());
234 //check if node belongs to same classification
235 Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
236 if(taxonSubtreeFilter!=null && !taxonSubtreeFilter.isEmpty()){
237 if(!taxonSubtreeFilter.iterator().next().getClassification().equals(taxonNode.getClassification())){
238 MessagingUtils.warningDialog(Messages.TaxonNodeDropAdapter_CLASSIFICATIONS_NO_MATCH, this.getClass(),
239 Messages.TaxonNodeDropAdapter_CLASSIFICATIONS_NO_MATCH_MESSAGE);
240 return;
241 }
242 }
243 Viewer taxonTreeViewer = getTaxonTreeViewer();
244 Object input = taxonTreeViewer.getInput();
245 Collection<TaxonNode> treeNodes;
246 if(input==null){
247 treeNodes = new ArrayList<>();
248 }
249 else{
250 treeNodes = (Collection<TaxonNode>) input;
251 }
252 treeNodes.add(taxonNode);
253 taxonTreeViewer.setInput(treeNodes);
254 dirty.setDirty(true);
255 }
256
257 public void removeSelectedTaxonNodes(){
258 IStructuredSelection selection = (IStructuredSelection) composite.getTaxonNodeTree().getSelection();
259 if(selection.toList().stream().anyMatch(object->!(object instanceof TaxonNode))){
260 MessagingUtils.warningDialog(Messages.DescriptiveDataSetEditor_DELETE_FAIL_TITLE, this.getClass(), Messages.DescriptiveDataSetEditor_DELETE_FAIL_MESSAGE);
261 return;
262 }
263 Iterator<Object> iterator = selection.iterator();
264 Viewer taxonTreeViewer = getTaxonTreeViewer();
265 Collection<TaxonNode> input = (Collection<TaxonNode>) taxonTreeViewer.getInput();
266 while(iterator.hasNext()){
267 Object next = iterator.next();
268 TaxonNode taxonNode = (TaxonNode) next;
269 input.remove(taxonNode);
270 descriptiveDataSet.removeTaxonSubtree(taxonNode);
271 }
272 taxonTreeViewer.setInput(input);
273 dirty.setDirty(true);
274 }
275
276 @Persist
277 @Override
278 public void save(IProgressMonitor monitor) {
279 Collection<TermDto> areas = composite.getAreas();
280 Object input = composite.getTaxonNodeTree().getInput();
281 if(input!=null){
282 descriptiveDataSet.setTaxonSubtreeFilter(new HashSet<>());//clear existing filter
283 Collection<ITaxonTreeNode> taxonNodes = (Collection<ITaxonTreeNode>) input;
284 for (ITaxonTreeNode taxonTreeNode : taxonNodes) {
285 if(taxonTreeNode instanceof TaxonNode){
286 TaxonNode taxonNode = (TaxonNode)taxonTreeNode;
287 descriptiveDataSet.addTaxonSubtree(taxonNode);
288 }
289 }
290 }
291 FeatureTree characters = composite.getCharacters();
292 if(characters!=null){
293 //save characters because they can be modified in this editor
294 characters.getDistinctFeatures().forEach(character->CdmStore.getService(ITermService.class).merge(character,true));
295 }
296
297
298 DefinedTermBase rankMaxSelection = composite.getRankMax().getSelection();
299 Rank rankMax = null;
300 if(rankMaxSelection instanceof Rank){
301 rankMax = (Rank) rankMaxSelection;
302 }
303 DefinedTermBase rankMinSelection = composite.getRankMin().getSelection();
304 Rank rankMin = null;
305 if(rankMinSelection instanceof Rank){
306 rankMin = (Rank) rankMinSelection;
307 }
308
309 descriptiveDataSet.setMaxRank(rankMax);
310 descriptiveDataSet.setMinRank(rankMin);
311 descriptiveDataSet.setDescriptiveSystem(characters);
312 List<DefinedTermBase> terms = CdmStore.getService(ITermService.class)
313 .load(areas.stream().map(area -> area.getUuid()).collect(Collectors.toList()), null);
314 Set<NamedArea> areaTerms = new HashSet<>();
315 terms.forEach(term->areaTerms.add((NamedArea) term));
316 descriptiveDataSet.setGeoFilter(areaTerms);
317
318 conversation.commit();
319 CdmStore.getService(IDescriptiveDataSetService.class).merge(descriptiveDataSet, true);
320
321 dirty.setDirty(false);
322 }
323
324 @Override
325 public boolean isDirty() {
326 return dirty.isDirty();
327 }
328
329 @PreDestroy
330 public void dispose() {
331 if (conversation != null) {
332 conversation.close();
333 conversation = null;
334 }
335 if(cdmEntitySession != null) {
336 cdmEntitySession.dispose();
337 cdmEntitySession = null;
338 }
339 dirty.setDirty(false);
340 }
341
342 @Focus
343 public void setFocus() {
344 if(composite!=null){
345 composite.setFocus();
346 }
347 if (getConversationHolder() != null) {
348 getConversationHolder().bind();
349 }
350 if(cdmEntitySession != null) {
351 cdmEntitySession.bind();
352 }
353 }
354
355 public Viewer getTaxonTreeViewer() {
356 return composite.getTaxonNodeTree();
357 }
358
359 public DescriptiveDataSet getDescriptiveDataSet() {
360 return descriptiveDataSet;
361 }
362
363 @Override
364 public void update(CdmDataChangeMap arg0) {
365 }
366
367 @Override
368 public ICdmEntitySession getCdmEntitySession() {
369 return cdmEntitySession;
370 }
371
372 @Override
373 public Collection<DescriptiveDataSet> getRootEntities() {
374 return Collections.singleton(descriptiveDataSet);
375 }
376
377 @Override
378 public Map<Object, List<String>> getPropertyPathsMap() {
379 return null;
380 }
381
382 @Override
383 public ConversationHolder getConversationHolder() {
384 return conversation;
385 }
386
387 @Override
388 public void changed(Object element) {
389 dirty.setDirty(true);
390 }
391
392 @Override
393 public void forceDirty() {
394 dirty.setDirty(true);
395 }
396
397 }