ref #6595 Fix selection propagation for name editor when entering text
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / name / e4 / TaxonNameEditorE4.java
1 /**
2 * Copyright (C) 2007 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
10 package eu.etaxonomy.taxeditor.editor.name.e4;
11
12 import java.util.ArrayList;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Set;
16
17 import javax.annotation.PostConstruct;
18 import javax.annotation.PreDestroy;
19 import javax.inject.Inject;
20
21 import org.apache.commons.lang.StringUtils;
22 import org.eclipse.core.commands.operations.IUndoContext;
23 import org.eclipse.core.commands.operations.UndoContext;
24 import org.eclipse.core.runtime.IProgressMonitor;
25 import org.eclipse.core.runtime.OperationCanceledException;
26 import org.eclipse.e4.core.contexts.IEclipseContext;
27 import org.eclipse.e4.core.services.events.IEventBroker;
28 import org.eclipse.e4.ui.di.Focus;
29 import org.eclipse.e4.ui.di.Persist;
30 import org.eclipse.e4.ui.model.application.ui.MDirtyable;
31 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
32 import org.eclipse.e4.ui.services.EMenuService;
33 import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
34 import org.eclipse.jface.dialogs.MessageDialog;
35 import org.eclipse.jface.viewers.ISelection;
36 import org.eclipse.jface.viewers.ISelectionProvider;
37 import org.eclipse.jface.viewers.StructuredSelection;
38 import org.eclipse.swt.dnd.DND;
39 import org.eclipse.swt.dnd.DropTarget;
40 import org.eclipse.swt.dnd.Transfer;
41 import org.eclipse.swt.graphics.Color;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.ui.ISelectionListener;
44 import org.eclipse.ui.IWorkbenchPart;
45 import org.eclipse.ui.IWorkbenchPartReference;
46 import org.eclipse.ui.forms.ManagedForm;
47 import org.eclipse.ui.forms.widgets.FormToolkit;
48 import org.eclipse.ui.forms.widgets.ScrolledForm;
49 import org.eclipse.ui.forms.widgets.TableWrapLayout;
50
51 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
52 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
53 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
54 import eu.etaxonomy.cdm.model.common.CdmBase;
55 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
56 import eu.etaxonomy.cdm.model.taxon.Taxon;
57 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
58 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
59 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
60 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
61 import eu.etaxonomy.taxeditor.editor.CdmDataTransfer;
62 import eu.etaxonomy.taxeditor.editor.ISecuredEditor;
63 import eu.etaxonomy.taxeditor.editor.ITaxonEditor;
64 import eu.etaxonomy.taxeditor.editor.e4.TaxonEditorInputE4;
65 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
66 import eu.etaxonomy.taxeditor.editor.name.e4.container.AbstractGroupE4;
67 import eu.etaxonomy.taxeditor.editor.name.e4.container.AbstractGroupedContainerE4;
68 import eu.etaxonomy.taxeditor.editor.name.e4.container.AcceptedGroupE4;
69 import eu.etaxonomy.taxeditor.editor.name.e4.container.AcceptedNameContainerE4;
70 import eu.etaxonomy.taxeditor.editor.name.e4.container.ContainerFactoryE4;
71 import eu.etaxonomy.taxeditor.editor.name.e4.container.HomotypicalSynonymGroupE4;
72 import eu.etaxonomy.taxeditor.editor.name.e4.container.MisappliedGroupE4;
73 import eu.etaxonomy.taxeditor.editor.name.e4.dnd.NameEditorDropTargetListenerE4;
74 import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
75 import eu.etaxonomy.taxeditor.model.AbstractUtility;
76 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
77 import eu.etaxonomy.taxeditor.model.IPartChangeListener;
78 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
79 import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
80 import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
81 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
82 import eu.etaxonomy.taxeditor.model.MessagingUtils;
83 import eu.etaxonomy.taxeditor.model.TaxeditorPartService;
84 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
85 import eu.etaxonomy.taxeditor.preference.Resources;
86 import eu.etaxonomy.taxeditor.security.RequiredPermissions;
87 import eu.etaxonomy.taxeditor.store.CdmStore;
88 import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
89
90 /**
91 *
92 * @author pplitzner
93 * @date Aug 24, 2017
94 *
95 */
96 public class TaxonNameEditorE4 implements IConversationEnabled, IDirtyMarkable, IPartContentHasDetails,
97 IPartContentHasSupplementalData, IPartContentHasMedia, IPartContentHasFactualData, IPartChangeListener,
98 ISelectionListener, ISecuredEditor, IPostOperationEnabled, IE4SavablePart, ITaxonEditor, IDropTargetableE4 {
99
100 private Taxon taxon;
101
102 private ManagedForm managedForm;
103 private ScrolledForm scrolledForm;
104 private Composite parent;
105 private ISelectionProvider simpleSelectionProvider;
106
107 private TaxonBase selection;
108
109 private ConversationHolder conversation;
110
111 private AcceptedGroupE4 acceptedGroup;
112 private List<HomotypicalSynonymGroupE4> heterotypicSynonymGroups = new ArrayList<>();
113 private MisappliedGroupE4 misappliedGroup;
114
115 private DropTarget target;
116
117 private TaxonBase objectAffectedByLastOperation;
118
119 @Inject
120 private EMenuService menuService;
121
122 @Inject
123 private ESelectionService selService;
124
125 @Inject
126 private IEclipseContext context;
127
128 @Inject
129 private MDirtyable dirty;
130
131 private MPart thisPart;
132
133 private TaxonEditorInputE4 input;
134
135 private UndoContext undoContext;
136
137 @Inject
138 private IEventBroker eventBroker;
139
140 @Inject
141 public TaxonNameEditorE4() {
142 undoContext = new UndoContext();
143 }
144
145
146 @PostConstruct
147 public void createPartControl(Composite parent, MPart thisPart) {
148 this.thisPart = thisPart;
149 if (CdmStore.isActive()){
150 if(conversation == null){
151 conversation = CdmStore.createConversation();
152 }
153 }
154 else{
155 return;
156 }
157
158 createManagedForm(parent);
159
160 TaxeditorPartService.getInstance().addListener(
161 TaxeditorPartService.PART_ACTIVATED, this);
162
163 }
164
165 protected void createManagedForm(Composite composite) {
166
167 managedForm = new ManagedForm(composite) {
168
169 @Override
170 public void dirtyStateChanged() {
171 dirty.setDirty(true);
172 }
173
174 @Override
175 public boolean setInput(Object input) {
176 if (input instanceof AbstractGroupedContainerE4) {
177 TaxonBase newSelection = ((AbstractGroupedContainerE4) input).getData();
178 if(selection!=newSelection || TaxonNameEditorE4.this.isDirty()){
179 selection = newSelection;
180 selService.setSelection(new StructuredSelection(selection));
181 }
182 }else if(input == null){
183 selection = null;
184 selService.setSelection(new StructuredSelection());
185 }
186
187
188 return super.setInput(input);
189 }
190 };
191
192 scrolledForm = managedForm.getForm();
193 parent = scrolledForm.getBody();
194
195 parent.setData(taxon);
196
197 TableWrapLayout layout = new TableWrapLayout();
198 layout.leftMargin = 0;
199 layout.rightMargin = 0;
200 layout.topMargin = 0;
201 layout.bottomMargin = 0;
202
203 layout.verticalSpacing = 0;
204 layout.horizontalSpacing = 0;
205
206 parent.setLayout(layout);
207 parent.setBackground(AbstractUtility
208 .getColor(Resources.COLOR_COMPOSITE_BACKGROUND));
209 }
210
211 public void createOrUpdateNameComposites() {
212 ContainerFactoryE4.createOrUpdateAcceptedTaxonsHomotypicGroup(this);
213 ContainerFactoryE4.createOrUpdateHeterotypicSynonymyGroups(this);
214 ContainerFactoryE4.createOrUpdateMisapplicationsGroup(this);
215
216
217 // Redraw composite
218 managedForm.reflow(true);
219 managedForm.refresh();
220 }
221
222 @Override
223 public Taxon getTaxon() {
224 return HibernateProxyHelper.deproxy(taxon);
225 }
226
227 public void setDirty() {
228 managedForm.dirtyStateChanged();
229 }
230
231 @Focus
232 public void setFocus() {
233 //make sure to bind again if maybe in another view the conversation was unbound
234 if(conversation!=null && !conversation.isBound()){
235 conversation.bind();
236 }
237 if(input!=null){
238 if (getSelectedContainer() == null) {
239 throw new IllegalStateException(
240 Messages.TaxonNameEditor_THERE_SHOULD_ALWAYS_BE);
241 }
242 getSelectedContainer().setSelected();
243
244 // check permissions
245 boolean doEnable = permissionsSatisfied();
246 managedForm.getForm().setEnabled(doEnable);
247 }
248 eventBroker.post(WorkbenchEventConstants.CURRENT_ACTIVE_EDITOR, this);
249 }
250
251 @Override
252 public boolean permissionsSatisfied() {
253 TaxonNode taxonNode = input.getTaxonNode();
254 boolean doEnable = CdmStore.currentAuthentiationHasPermission(taxonNode, RequiredPermissions.TAXONNODE_EDIT);
255 return doEnable;
256 }
257
258 @Override
259 public ConversationHolder getConversationHolder() {
260 return conversation;
261 }
262
263 /** {@inheritDoc} */
264 @Override
265 public void update(CdmDataChangeMap events) {
266 // redraw();
267 }
268
269 /**
270 * Redraws this editor return true on success
271 *
272 * @return a boolean.
273 */
274 public boolean redraw() {
275 return redraw(true);
276 }
277
278 /**
279 * {@inheritDoc}
280 *
281 * Redraws the editor controls
282 */
283 public boolean redraw(boolean focus) {
284
285 createOrUpdateNameComposites();
286
287 if (focus) {
288 setFocus();
289 }
290
291 return true;
292 }
293
294 @Override
295 public boolean postOperation(CdmBase objectAffectedByOperation) {
296
297 changed(objectAffectedByOperation);
298
299 redraw(false);
300
301 if (objectAffectedByOperation instanceof TaxonBase) {
302 objectAffectedByLastOperation = (TaxonBase) objectAffectedByOperation;
303 }
304
305 return true;
306 }
307
308 public ManagedForm getManagedForm() {
309 return managedForm;
310 }
311
312
313 /**
314 * <p>
315 * checkForEmptyNames
316 * </p>
317 *
318 * @return true if there are empty names
319 */
320 public boolean checkForEmptyNames() {
321 for (AbstractGroupedContainerE4 container : getGroupedContainers()) {
322 if (container.getName() == null
323 || StringUtils.isEmpty(container.getName().getTitleCache())) {
324 return true;
325 }
326 }
327 return false;
328 }
329
330 public Set<AbstractGroupedContainerE4> getEmptyContainers() {
331 Set<AbstractGroupedContainerE4> containersWithEmptyNames = new HashSet<>();
332
333 for (AbstractGroupedContainerE4 container : getGroupedContainers()) {
334 if (container.getName() == null
335 || StringUtils.isEmpty(container.getName().getTitleCache())) {
336 containersWithEmptyNames.add(container);
337 }
338 }
339
340 return containersWithEmptyNames;
341 }
342
343 /** {@inheritDoc} */
344 @Override
345 @Persist
346 public void save(IProgressMonitor monitor) {
347
348 monitor.beginTask(Messages.TaxonNameEditor_SAVING_NAMES, getGroupedContainers().size());
349 if (!conversation.isBound()) {
350 conversation.bind();
351 }
352 monitor.worked(1);
353
354 // check for empty names
355 if (checkForEmptyNames()) {
356 MessageDialog.openWarning(AbstractUtility.getShell(), Messages.MultiPageTaxonEditor_NO_NAME_SPECIFIED,
357 Messages.MultiPageTaxonEditor_NO_NAME_SPECIFIED_MESSAGE);
358 return;
359 }
360 for (AbstractGroupedContainerE4 container : getGroupedContainers()) {
361
362 monitor.subTask(Messages.TaxonNameEditor_SAVING_COMPOSITES
363 + container.getTaxonBase().getTitleCache());
364 container.persistName();
365
366 // In case the progress monitor was canceled throw an exception.
367 if (monitor.isCanceled()) {
368 throw new OperationCanceledException();
369 }
370
371 // Otherwise declare this step as done.
372 monitor.worked(1);
373
374 }
375 input.merge();
376 // commit the conversation and start a new transaction immediately
377 conversation.commit(true);
378
379
380 dirty.setDirty(false);
381
382 // Stop the progress monitor.
383 monitor.done();
384 }
385
386 public void init(TaxonEditorInputE4 input) {
387
388 if (!(input != null)) {
389 MessagingUtils.error(this.getClass(), new Exception(Messages.TaxonNameEditor_INVALID_INPUT));
390 return;
391 }
392
393 if (input.getAdapter(Taxon.class) != null) {
394 taxon = CdmBase.deproxy(input.getAdapter(Taxon.class), Taxon.class);
395 } else {
396 MessagingUtils.error(this.getClass(), new Exception(Messages.TaxonNameEditor_INVALID_INPUT_TAXON_NULL));
397 return;
398 }
399
400 this.input = input;
401
402 createOrUpdateNameComposites();
403
404 createDragSupport();
405
406 setPartName();
407
408 //set initial selection
409 TaxonBase initiallySelectedTaxonBase = input.getInitiallySelectedTaxonBase();
410 if(initiallySelectedTaxonBase!=null){
411 selService.setSelection(new StructuredSelection(initiallySelectedTaxonBase));
412 getContainer(initiallySelectedTaxonBase).setSelected();
413 }
414 }
415
416 private void createDragSupport() {
417 // Listen for names being dragged outside of existing homotypic groups -
418 // user wants to create a new group
419 Transfer[] types = new Transfer[] { CdmDataTransfer.getInstance() };
420 int operations = DND.DROP_MOVE;
421 if (target == null) {
422 target = new DropTarget(parent, operations);
423 target.setTransfer(types);
424 target.addDropListener(new NameEditorDropTargetListenerE4(this));
425 }
426 }
427
428 public AcceptedNameContainerE4 getAcceptedNameContainer() {
429 return getAcceptedGroup().getAcceptedNameContainer();
430 }
431
432 public HomotypicalSynonymGroupE4 getHomotypicalGroupContainer(
433 HomotypicalGroup homotypicalGroup) {
434 for (HomotypicalSynonymGroupE4 group : getHeterotypicSynonymGroups()) {
435 if (group.getGroup().equals(homotypicalGroup)) {
436 return group;
437 }
438 }
439
440 return null;
441 }
442
443 /**
444 * <p>
445 * getDirtyNames
446 * </p>
447 *
448 * @return a Set containing all composites that have been edited
449 */
450 public Set<AbstractGroupedContainerE4> getDirtyNames() {
451 Set<AbstractGroupedContainerE4> dirtyNames = new HashSet<>();
452
453 for (AbstractGroupedContainerE4 composite : getGroupedContainers()) {
454 if (composite.isDirty()) {
455 dirtyNames.add(composite);
456 }
457 }
458
459 return dirtyNames;
460 }
461
462 public List<AbstractGroupedContainerE4> getGroupedContainers() {
463 List<AbstractGroupedContainerE4> groupedComposites = new ArrayList<>();
464
465 for (AbstractGroupE4 group : getAllGroups()) {
466 if (group!= null){
467 groupedComposites.addAll(group.getGroupedContainers());
468 }
469 }
470
471 return groupedComposites;
472 }
473
474 public List<AbstractGroupE4> getAllGroups() {
475 List<AbstractGroupE4> allGroups = new ArrayList<>();
476
477 allGroups.add(getAcceptedGroup());
478
479 heterotypicSynonymGroups = getHeterotypicSynonymGroups();
480
481 if (heterotypicSynonymGroups != null) {
482 allGroups.addAll(heterotypicSynonymGroups);
483 }
484
485 if (misappliedGroup != null) {
486 allGroups.add(misappliedGroup);
487 }
488
489 return allGroups;
490 }
491
492 @Override
493 public IEclipseContext getContext() {
494 return context;
495 }
496
497 public boolean isDirty() {
498 return dirty.isDirty();
499 }
500
501 @PreDestroy
502 public void dispose() {
503 if(conversation!=null){
504 conversation.unregisterForDataStoreChanges(this);
505 conversation.close();
506 }
507 eventBroker.post(WorkbenchEventConstants.CURRENT_ACTIVE_EDITOR, null);
508 }
509
510 /** {@inheritDoc} */
511 @Override
512 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
513
514 }
515
516 public AbstractGroupedContainerE4 getSelectedContainer() {
517 return (selection != null) ? getContainer(selection)
518 : getAcceptedNameContainer();
519 }
520
521 @Override
522 public void dragEntered() {
523 // TODO change this
524 getControl().setBackground(
525 AbstractUtility.getColor(Resources.COLOR_DRAG_ENTER));
526 }
527
528 @Override
529 public void dragLeft() {
530 getControl().setBackground(
531 AbstractUtility.getColor(Resources.COLOR_COMPOSITE_BACKGROUND));
532 }
533
534
535 public void setMisapplicationsGroup(MisappliedGroupE4 misappliedGroup) {
536 this.misappliedGroup = misappliedGroup;
537 }
538
539 public FormToolkit getToolkit() {
540 return managedForm.getToolkit();
541 }
542
543 public List<HomotypicalSynonymGroupE4> getHeterotypicSynonymGroups() {
544 return heterotypicSynonymGroups;
545 }
546
547 public void addHeterotypicSynonymGroup(HomotypicalSynonymGroupE4 group) {
548 heterotypicSynonymGroups.add(group);
549 }
550
551 public AcceptedGroupE4 getAcceptedGroup() {
552 return acceptedGroup;
553 }
554
555 public void setAcceptedGroup(AcceptedGroupE4 acceptedGroup) {
556 this.acceptedGroup = acceptedGroup;
557 }
558
559 public MisappliedGroupE4 getMisappliedGroup() {
560 return misappliedGroup;
561 }
562
563 public boolean isActive() {
564 return this.equals(AbstractUtility.getActivePart());
565 }
566
567 @Override
568 public boolean onComplete() {
569 getContainer(objectAffectedByLastOperation).setSelected();
570 return true;
571 }
572
573 /** {@inheritDoc} */
574 @Override
575 public void partChanged(Integer eventType, IWorkbenchPartReference partRef) {
576 if (!partRef.getPart(false).equals(this)) {
577 // getSelectedObject().colorSelected(AbstractGroupedContainer.SELECTED_NO_FOCUS);
578 }
579 }
580
581 public void removeGroup(AbstractGroupE4 group) {
582 if (group != null) {
583 group.dispose();
584
585 //if (heterotypicSynonymGroups != null) {
586 heterotypicSynonymGroups.remove(group);
587 //}
588 }
589 }
590
591 public AbstractGroupedContainerE4 getContainer(TaxonBase taxonBase) {
592 List<AbstractGroupedContainerE4> groupedContainers = getGroupedContainers();
593 for (AbstractGroupedContainerE4 container : groupedContainers) {
594 if (container.getData().equals(taxonBase)
595 && container.getNameViewer().getTextWidget() != null) {
596 return container;
597 }
598 }
599 return getAcceptedNameContainer();
600 }
601
602 public void setOnError() {
603 Color disabledColor = AbstractUtility.getColor(Resources.COLOR_EDITOR_ERROR);
604 setEnabled(false, disabledColor);
605 }
606
607 public void setDisabled(){
608 Color disabledColor = AbstractUtility.getColor(Resources.COLOR_TEXT_DISABLED_BACKGROUND);
609 setEnabled(false, disabledColor);
610 }
611
612 protected void setEnabled(boolean enabled, Color background) {
613
614 for(AbstractGroupedContainerE4 groupedContainer : getGroupedContainers()){
615 groupedContainer.setEnabled(enabled);
616 }
617
618 // send an empty selection to the current provider - TODO only on error ???
619 if (!enabled) {
620 getManagedForm().setInput(null);
621
622 for (AbstractGroupedContainerE4 groupedContainer : getGroupedContainers()) {
623 groupedContainer.setBackground(background);
624 }
625 }
626 getControl().setBackground(background);
627 }
628
629 @Override
630 public void changed(Object element) {
631 // setDirty(true);
632 // if the attribute is null then do not set the dirty flag -> hotfix for the problem that for tasks done in service methods the changes are saved automatically
633 if (element != null){
634 dirty.setDirty(true);
635 //refresh part title
636 //TODO: refresh taxon node in taxon navigator
637 setPartName();
638 }
639
640 if (element instanceof TaxonBase) {
641 AbstractGroupedContainerE4 container = getContainer((TaxonBase) element);
642 if (container != null) {
643 container.refresh();
644 }
645 }
646 if (element instanceof TaxonRelationship) {
647 AbstractGroupedContainerE4 container = getContainer(((TaxonRelationship) element).getFromTaxon());
648 if (container != null) {
649 container.refresh();
650 }
651 }
652 }
653
654 public void setPartName(){
655 thisPart.setLabel(this.taxon.getName().getFullTitleCache());
656 }
657
658 @Override
659 public void forceDirty() {
660 setDirty();
661 }
662
663
664 public IUndoContext getUndoContext() {
665 return undoContext;
666 }
667
668 @Override
669 public Composite getControl(){
670 return managedForm.getForm().getBody();
671 }
672
673 public EMenuService getMenuService() {
674 return menuService;
675 }
676
677 public ESelectionService getSelectionService() {
678 return selService;
679 }
680
681
682 /**
683 * {@inheritDoc}
684 */
685 @Override
686 public boolean canAttachMedia() {
687 return true;
688 }
689
690 public TaxonEditorInputE4 getEditorInput() {
691 return input;
692 }
693
694 /**
695 * {@inheritDoc}
696 */
697 @Override
698 public TaxonNameEditorE4 getEditor() {
699 return this;
700 }
701
702 }