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