Merge branch 'release/4.3.0'
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / name / container / AbstractGroupedContainer.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.container;
11
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Set;
15
16 import org.eclipse.core.runtime.Assert;
17 import org.eclipse.jface.dialogs.Dialog;
18 import org.eclipse.jface.text.IDocument;
19 import org.eclipse.jface.text.Position;
20 import org.eclipse.jface.window.DefaultToolTip;
21 import org.eclipse.swt.custom.StyledText;
22 import org.eclipse.swt.dnd.DND;
23 import org.eclipse.swt.dnd.DragSource;
24 import org.eclipse.swt.dnd.Transfer;
25 import org.eclipse.swt.events.ControlAdapter;
26 import org.eclipse.swt.events.ControlEvent;
27 import org.eclipse.swt.events.ControlListener;
28 import org.eclipse.swt.events.FocusAdapter;
29 import org.eclipse.swt.events.FocusEvent;
30 import org.eclipse.swt.events.FocusListener;
31 import org.eclipse.swt.events.KeyEvent;
32 import org.eclipse.swt.events.ModifyEvent;
33 import org.eclipse.swt.events.ModifyListener;
34 import org.eclipse.swt.events.MouseAdapter;
35 import org.eclipse.swt.events.MouseEvent;
36 import org.eclipse.swt.graphics.Color;
37 import org.eclipse.swt.graphics.Font;
38 import org.eclipse.swt.graphics.Image;
39 import org.eclipse.swt.widgets.Composite;
40 import org.eclipse.swt.widgets.Control;
41 import org.eclipse.swt.widgets.Display;
42 import org.eclipse.swt.widgets.Label;
43 import org.eclipse.swt.widgets.Menu;
44 import org.eclipse.ui.forms.IFormPart;
45 import org.eclipse.ui.forms.IManagedForm;
46 import org.eclipse.ui.forms.widgets.TableWrapData;
47 import org.eclipse.ui.forms.widgets.TableWrapLayout;
48
49 import eu.etaxonomy.cdm.common.CdmUtils;
50 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
51 import eu.etaxonomy.cdm.model.name.NameRelationship;
52 import eu.etaxonomy.cdm.model.name.NonViralName;
53 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
54 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
55 import eu.etaxonomy.cdm.strategy.parser.ParserProblem;
56 import eu.etaxonomy.taxeditor.editor.CdmDataTransfer;
57 import eu.etaxonomy.taxeditor.editor.EditorUtil;
58 import eu.etaxonomy.taxeditor.editor.name.TaxonNameEditor;
59 import eu.etaxonomy.taxeditor.editor.name.container.EditorAnnotation.EditorAnnotationType;
60 import eu.etaxonomy.taxeditor.editor.name.dnd.NameEditorDragListener;
61 import eu.etaxonomy.taxeditor.editor.name.dnd.NameEditorDragSourceEffect;
62 import eu.etaxonomy.taxeditor.editor.name.operation.CreateSynonymInNewGroupOperation;
63 import eu.etaxonomy.taxeditor.labels.ILabelImageStrategy;
64 import eu.etaxonomy.taxeditor.labels.LabelImageProvider;
65 import eu.etaxonomy.taxeditor.model.IElementHasDetails;
66 import eu.etaxonomy.taxeditor.model.NameHelper;
67 import eu.etaxonomy.taxeditor.model.TextHelper;
68 import eu.etaxonomy.taxeditor.parser.ParseHandler;
69 import eu.etaxonomy.taxeditor.preference.Resources;
70
71 /**
72 * Formats <code>GroupedComposite</code> with cosmetic and layout properties
73 * specific to the Editor. This should be used to maintain a consistent look and
74 * feel for all Editor freetext area components, such as
75 * DescriptionElementComposite.
76 * <p>
77 * Requires an <code>IManagedForm</code>, whose <code>input</code> is set to the
78 * contents of {@link #getData()} when the <code>GroupedComposite</code> gets
79 * focus, i.e. to populate the property sheet with the data.
80 * </p>
81 * <p>
82 * The <code>IManagedForm</code> is also required to have a <code>Taxon</code>
83 * in its own <code>getData()</code>.
84 * </p>
85 * <p>
86 * The <code>IManagedForm</code> can also used for drawing borders by calling
87 * the method <code>createBorderSupport()</code>.
88 * </p>
89 *
90 * @author p.ciardelli
91 * @author n.hoffmann
92 * @created 02.06.2008
93 * @version 1.0
94 */
95 abstract public class AbstractGroupedContainer<T extends TaxonBase> implements
96 IFormPart, IContainerConstants, IElementHasDetails {
97
98 protected ParseHandler parseHandler;
99
100 private FocusListener nameCompositeFocusListener;
101 private ModifyListener nameCompositeModifyListener;
102
103 protected NameViewer nameViewer;
104
105 private AbstractGroup group;
106
107 private Label nonEditableInfoLabel;
108 private DefaultToolTip nonEditableInfoHover;
109
110 private static AbstractGroupedContainer selection;
111
112 private FocusListener focusListener;
113 private LineBreakListener lineBreakListener;
114
115 private int cursorPosition;
116
117 protected Composite control;
118
119 private Color backgroundColor;
120 private boolean isDirty;
121
122 /**
123 * <p>
124 * Constructor for AbstractGroupedContainer.
125 * </p>
126 *
127 * @param editor
128 * a {@link eu.etaxonomy.taxeditor.editor.name.TaxonNameEditor}
129 * object.
130 * @param group
131 * a
132 * {@link eu.etaxonomy.taxeditor.editor.name.container.AbstractGroup}
133 * object.
134 * @param taxonBase
135 * a T object.
136 * @param <T>
137 * a T object.
138 */
139 public AbstractGroupedContainer(T taxonBase) {
140 setData(taxonBase);
141 parseHandler = ParseHandler.NewInstance(taxonBase.getName());
142 }
143
144 public void createContent() {
145 createControl();
146
147 createTextViewer();
148 createLineWrapSupport();
149 createLineBreakListener();
150
151 setMenu(getEditor().getMenu());
152
153 setDraggableControl(new Control[] { getControl(),
154 getNameViewer().getRulerControl() });
155
156 createEmptyViewerPrompt(EMPTY_NAME_PROMPT);
157
158 initializeComposite();
159
160 createListener();
161
162 enableFreeText();
163 }
164
165 /**
166 * <p>
167 * createListener
168 * </p>
169 */
170 protected void createListener() {
171 nameCompositeModifyListener = new ModifyListener() {
172
173 public void modifyText(ModifyEvent e) {
174 // mark the composite dirty
175 setDirty(true);
176 // parse the text
177 String text = nameViewer.getTextWidget().getText();
178
179 NonViralName name = parseHandler.parse(text);
180 getTaxonBase().setName(name);
181 getTaxonBase().setTitleCache((getTaxonBase().generateTitle()));
182
183 // show errors resulting from parsing
184 calculateAnnotations();
185 // store the position of the cursor
186 storeCursor();
187 // notify selection listener
188 setDelayedSelection();
189 }
190 };
191 nameCompositeFocusListener = new FocusAdapter() {
192
193 /*
194 * (non-Javadoc)
195 *
196 * @see
197 * org.eclipse.swt.events.FocusAdapter#focusLost(org.eclipse.swt
198 * .events.FocusEvent)
199 */
200 @Override
201 public void focusLost(FocusEvent e) {
202 super.focusLost(e);
203
204 persistName();
205 }
206 };
207
208 addListener();
209 }
210
211 private void addListener() {
212 getNameViewer().getTextWidget().addModifyListener(
213 nameCompositeModifyListener);
214 getNameViewer().getTextWidget().addFocusListener(
215 nameCompositeFocusListener);
216 }
217
218 private void removeListener() {
219 getNameViewer().getTextWidget().removeModifyListener(
220 nameCompositeModifyListener);
221 getNameViewer().getTextWidget().removeFocusListener(
222 nameCompositeFocusListener);
223 }
224
225 /**
226 * Initialize the composite specific code
227 */
228 protected abstract void initializeComposite();
229
230 /**
231 * <p>
232 * getEmptyTextPrompt
233 * </p>
234 *
235 * @return a {@link java.lang.String} object.
236 */
237 protected String getEmptyTextPrompt() {
238 return EMPTY_NAME_PROMPT;
239 }
240
241 /**
242 *
243 */
244 private void showNameRelations() {
245 TaxonNameBase<?, ?> name = getName();
246 if (name == null) {
247 return;
248 }
249
250 ILabelImageStrategy strategy = LabelImageProvider
251 .getLabelStrategy(name);
252 LabelImageProvider labelProvider = new LabelImageProvider(strategy);
253
254 Set<NameRelationship> nameRelations = name.getNameRelations();
255 if (nameRelations.size() == 0) {
256 return;
257 }
258 // for (NameRelationship nameRelation : nameRelations) {
259 // String typeLabel = null;
260 // TaxonNameBase<?, ?> relatedName = null;
261 //
262 // if (name.equals(nameRelation.getFromName())) {
263 // typeLabel = labelProvider.getNameRelationTypeLabel(
264 // nameRelation.getType());
265 // relatedName = nameRelation.getToName();
266 // } else {
267 // typeLabel = labelProvider.getNameRelationTypeInverseLabel(
268 // nameRelation.getType());
269 // relatedName = nameRelation.getFromName();
270 // }
271 //
272 // setNonEditableInfo(typeLabel + " " +
273 // NameHelper.getDisplayName(relatedName));
274 // }
275 }
276
277 /**
278 * <p>
279 * initTextViewer
280 * </p>
281 */
282 protected void initTextViewer() {
283
284 // showNameRelations();
285
286 updateIndent();
287
288 updateIcon();
289
290 String text = NameHelper.getDisplayNameWithRef(getData());
291
292 if (text.length() == 0) {
293 initEmptyText();
294 } else {
295 getNameViewer().setText(text);
296 placeCursor();
297 }
298 calculateAnnotations();
299 }
300
301 /**
302 * <p>
303 * calculateErrors
304 * </p>
305 */
306 synchronized protected void calculateAnnotations() {
307 getNameViewer().clearAnnotations();
308 showAnnotations();
309 }
310
311 /**
312 *
313 */
314 public void showAnnotations() {
315
316 if (getName().hasProblem()) {
317 showParsingProblems();
318 }
319
320 if (!isNameParsable()) {
321 getNameViewer()
322 .addAnnotation(
323 new EditorAnnotation(EditorAnnotationType.WARNING,
324 0,
325 "This name may only be edited in the details view."));
326 }
327
328 if (isNameUsedMultipleTimes()) {
329 getNameViewer().addAnnotation(
330 new EditorAnnotation(EditorAnnotationType.WARNING, 0,
331 "This taxons name is used multiple times."));
332 }
333
334 }
335
336 /**
337 *
338 */
339 private void showParsingProblems() {
340 String text = getNameViewer().getTextWidget().getText();
341
342 List<ParserProblem> parsingProblems = getName().getParsingProblems();
343
344 for (ParserProblem problem : parsingProblems) {
345 getNameViewer().addAnnotation(new EditorAnnotation(problem),
346 getParsingProblemPosition());
347 }
348 }
349
350 private Position getParsingProblemPosition() {
351 String text = getNameViewer().getTextWidget().getText();
352
353 if (getName().hasProblem() && text.length() > 0) {
354 int start = getName().getProblemStarts();
355 int length = getName().getProblemEnds() - start;
356
357 if (start == -1 || getName().getProblemEnds() == -1) {
358 return null;
359 }
360
361 // Don't let squigglies try to draw beyond the end of the text
362 if (text.length() < start + length) {
363 length = text.length() - start;
364 }
365 if (length<0){
366 return null;
367 }
368 return new Position(start, length);
369 }
370 return null;
371 }
372
373 /**
374 * <p>
375 * handleSplitText
376 * </p>
377 *
378 * @param text
379 * a {@link java.lang.String} object.
380 */
381 protected void handleSplitText(String text) {
382 // Create a synonym in a new homotypic group using text as name
383 TaxonNameBase synonymName = ParseHandler
384 .parseReferencedName(text, null);
385
386 EditorUtil.executeOperation(new CreateSynonymInNewGroupOperation(
387 "New Heterotypic Synonym", getEditor().getUndoContext(),
388 getEditor().getTaxon(), synonymName, getEditor()));
389 }
390
391 /**
392 * Refreshes the display with latest data from the model.
393 *
394 * Note: Will not parse the text and not calculate errors!
395 */
396 public void refresh() {
397 // showNameRelations();
398
399 String text = NameHelper.getDisplayNameWithRef(getTaxonBase());
400
401 if (getNameViewer().getTextWidget() == null) {
402 // we might get here via dnd. Look slike it can be ignored
403 return;
404 }
405
406 if (text.length() == 0) {
407 initEmptyText();
408 } else if (!getNameViewer().getTextWidget().getText().equals(text)) {
409 removeListener();
410 getNameViewer().getTextWidget().setText(text);
411 addListener();
412 }
413
414 updateNonEditableInfo();
415
416 updateIcon();
417 // placeCursor();
418 updateIndent();
419
420 enableFreeText();
421 }
422
423 /**
424 *
425 */
426 protected abstract void updateIcon();
427
428 protected abstract void updateIndent();
429
430 /**
431 * <p>
432 * updateNonEditableInfo
433 * </p>
434 */
435 protected abstract void updateNonEditableInfo();
436
437 /**
438 *
439 */
440 private void enableFreeText() {
441 setEnabled(isFreetextEditingAllowed());
442 }
443
444 /**
445 * Checks whether the freetext should be editable based on specific empty
446 * fields.
447 *
448 * @return
449 */
450 private boolean isFreetextEditingAllowed() {
451 NonViralName name = (NonViralName) HibernateProxyHelper
452 .deproxy(getName());
453 boolean enableFreetext = true;
454
455 enableFreetext |= isNameUsedMultipleTimes();
456 enableFreetext &= isNameParsable();
457
458 return enableFreetext;
459 }
460
461 /**
462 * Checks whether there are more than one, non-orphaned taxon bases
463 * attached to the taxon name
464 *
465 * @return
466 */
467 private boolean isNameUsedMultipleTimes() {
468
469 Set<TaxonBase> taxonBases = getName().getTaxonBases();
470 Iterator<TaxonBase> tbItr = taxonBases.iterator();
471 int nonOrphanedTaxonBaseCount = taxonBases.size();
472
473 while(tbItr.hasNext()) {
474 TaxonBase tb = tbItr.next();
475 if(tb.isOrphaned()) {
476 nonOrphanedTaxonBaseCount--;
477 }
478 }
479 if(nonOrphanedTaxonBaseCount > 1) {
480 return true;
481 }
482 return false;
483 }
484
485 private boolean isNameParsable() {
486 TaxonNameBase name = getName();
487
488 boolean isParsable = true;
489 isParsable &= CdmUtils.isEmpty(name.getAppendedPhrase()); // taxonFieldsEmpty();
490
491 if (name instanceof NonViralName) {
492 NonViralName nonViralName = (NonViralName) name;
493 isParsable &= !nonViralName.isProtectedAuthorshipCache();
494 isParsable &= !nonViralName.isProtectedNameCache();
495 }
496
497 return isParsable;
498 }
499
500 /**
501 * Parse the text and calculate errors
502 */
503 public void parseAndCalculateAnnotations() {
504 removeListener();
505 String unparsedNameString = getNameViewer().getTextWidget().getText();
506 parseHandler.parse(unparsedNameString);
507 addListener();
508 calculateAnnotations();
509 }
510
511 /**
512 * <p>
513 * getTaxonBase
514 * </p>
515 *
516 * @return the taxonBase
517 */
518 public T getTaxonBase() {
519 return getData();
520 }
521
522 /**
523 * <p>
524 * getName
525 * </p>
526 *
527 * @return a {@link eu.etaxonomy.cdm.model.name.TaxonNameBase} object.
528 */
529 public TaxonNameBase getName() {
530 return (TaxonNameBase) HibernateProxyHelper.deproxy(getTaxonBase()
531 .getName());
532 }
533
534 /**
535 * <p>
536 * persistName
537 * </p>
538 */
539 public void persistName() {
540 if (isDirty()) {
541 getNameViewer().getTextWidget().setEnabled(false);
542 final String unparsedNameString = getNameViewer().getTextWidget()
543 .getText();
544 // Job job = new Job("Persisting Name"){
545 //
546 // @Override
547 // protected IStatus run(IProgressMonitor monitor) {
548 //
549 final NonViralName name = parseHandler
550 .parseAndResolveDuplicates(unparsedNameString);
551 //
552 // Display.getDefault().asyncExec(new Runnable(){
553 // public void run() {
554 getTaxonBase().setName(name);
555 getTaxonBase().setTitleCache((getTaxonBase().generateTitle()));
556 setDirty(false);
557 getNameViewer().getTextWidget().setEnabled(true);
558 // };
559 // });
560 //
561 //
562 // return Status.OK_STATUS;
563 // }
564 //
565 // };
566 //
567 // job.setPriority(Job.DECORATE);
568 // job.schedule();
569 }
570 }
571
572 /**
573 * <p>
574 * Getter for the field <code>group</code>.
575 * </p>
576 *
577 * @return a
578 * {@link eu.etaxonomy.taxeditor.editor.name.container.AbstractGroup}
579 * object.
580 */
581 public AbstractGroup getGroup() {
582 if (group == null) {
583 throw new IllegalStateException("Group shall not be null.");
584 }
585 return group;
586 }
587
588 /**
589 * <p>
590 * remove
591 * </p>
592 */
593 public void remove() {
594 getGroup().remove(this);
595 }
596
597 /**
598 * <p>
599 * createControl
600 * </p>
601 */
602 protected void createControl() {
603 control = getEditor().getToolkit().createComposite(
604 getGroup().getControl());
605
606 control.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
607 TableWrapLayout layout = new TableWrapLayout();
608 layout.leftMargin = 0;
609 layout.rightMargin = 0;
610 layout.topMargin = 5;
611 layout.bottomMargin = 5;
612
613 layout.verticalSpacing = 0;
614 layout.horizontalSpacing = 0;
615
616 control.setLayout(layout);
617
618 }
619
620 /**
621 * @return
622 */
623 protected TaxonNameEditor getEditor() {
624 return getGroup().getEditor();
625 }
626
627 /**
628 * <p>
629 * Getter for the field <code>control</code>.
630 * </p>
631 *
632 * @return a {@link org.eclipse.swt.widgets.Composite} object.
633 */
634 public Composite getControl() {
635 return control;
636 }
637
638 /**
639 * <p>
640 * createLineWrapSupport
641 * </p>
642 */
643 protected void createLineWrapSupport() {
644 new LineWrapSupport(getNameViewer(), getEditor().getManagedForm());
645 }
646
647 /**
648 * <p>
649 * createTextViewer
650 * </p>
651 */
652 protected void createTextViewer() {
653 nameViewer = new NameViewer(control);
654
655 focusListener = new FocusAdapter() {
656 @Override
657 public void focusGained(FocusEvent e) {
658 if(!enabled){
659 return;
660 }
661 for (AbstractGroupedContainer container : getEditor()
662 .getGroupedContainers()) {
663 container.colorSelected(NOT_SELECTED);
664 }
665 getEditor().getManagedForm().setInput(
666 AbstractGroupedContainer.this);
667 placeCursor();
668 colorSelected(SELECTED_FOCUS);
669 }
670 };
671 nameViewer.getTextWidget().addFocusListener(focusListener);
672
673 //
674 MouseAdapter mouseListener = new MouseAdapter() {
675 @Override
676 public void mouseDown(MouseEvent e) {
677 storeCursor();
678 }
679 };
680 control.addMouseListener(mouseListener);
681 nameViewer.getRulerControl().addMouseListener(mouseListener);
682 nameViewer.getTextWidget().addMouseListener(mouseListener);
683 }
684
685 /**
686 * <p>
687 * setIcon
688 * </p>
689 *
690 * @param icon
691 * a {@link org.eclipse.swt.graphics.Image} object.
692 */
693 public void setIcon(Image icon) {
694 getNameViewer().setIcon(icon);
695 }
696
697 /**
698 * <p>
699 * setIndent
700 * </p>
701 *
702 * @param indent
703 * a int.
704 */
705 public void setIndent(int indent) {
706 if (control.getLayout() instanceof TableWrapLayout) {
707 TableWrapLayout layout = ((TableWrapLayout) control.getLayout());
708 layout.leftMargin = indent;
709 layout.rightMargin = ACCEPTED_INDENT;
710 control.setLayout(layout);
711 control.layout();
712 } else {
713 new RuntimeException(
714 "Couldn't indent - composite's layout must be TableWrapLayout.");
715 }
716 }
717
718 /**
719 * <p>
720 * setSelected
721 * </p>
722 */
723 public void setSelected() {
724 getNameViewer().getTextWidget().setFocus();
725 }
726
727 /**
728 * <p>
729 * isSelected
730 * </p>
731 *
732 * @return a boolean.
733 */
734 public boolean isSelected() {
735 return getEditor().getSelectedContainer() == this;
736 }
737
738 /**
739 * <p>
740 * colorSelected
741 * </p>
742 *
743 * @param mode
744 * a int.
745 */
746 public void colorSelected(int mode) {
747 if (!control.isDisposed()) {
748 String colorString = null;
749
750 switch (mode) {
751 case SELECTED_FOCUS:
752 colorString = Resources.COLOR_CONTROL_SELECTED_FOCUS;
753 break;
754 case SELECTED_NO_FOCUS:
755 colorString = Resources.COLOR_CONTROL_SELECTED;
756 break;
757 default:
758 colorString = Resources.COLOR_COMPOSITE_BACKGROUND;
759 }
760
761 backgroundColor = EditorUtil.getColor(colorString);
762
763 setBackground(backgroundColor);
764 }
765 }
766
767 /**
768 * <p>
769 * setDelayedSelection
770 * </p>
771 */
772 protected void setDelayedSelection() {
773 // TODO this might be done better
774 // this is the quickest solution i could come up with and it improves
775 // performance
776 // please reimplement if you know better.
777 selection = this;
778
779 // start timer
780 Display display = Display.getCurrent();
781 Runnable runnable = new Runnable() {
782
783 public void run() {
784 getEditor().getManagedForm().setInput(selection);
785 }
786 };
787 display.timerExec(1000, runnable);
788
789 }
790
791 /**
792 * <p>
793 * setBackground
794 * </p>
795 *
796 * @param color
797 * a {@link org.eclipse.swt.graphics.Color} object.
798 */
799 public void setBackground(Color color) {
800 control.setBackground(color);
801
802 for (Control child : control.getChildren()) {
803 child.setBackground(color);
804 }
805
806 getNameViewer().setBackground(color);
807 }
808
809 /*
810 * (non-Javadoc)
811 *
812 * @see
813 * org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
814 */
815 /**
816 * <p>
817 * setFont
818 * </p>
819 *
820 * @param font
821 * a {@link org.eclipse.swt.graphics.Font} object.
822 */
823 public void setFont(Font font) {
824 getNameViewer().getTextWidget().setFont(font);
825 }
826
827 /**
828 * <p>
829 * Getter for the field <code>nameViewer</code>.
830 * </p>
831 *
832 * @return a {@link eu.etaxonomy.taxeditor.editor.name.container.NameViewer}
833 * object.
834 */
835 public NameViewer getNameViewer() {
836 if (nameViewer == null) {
837 throw new RuntimeException(
838 "The Name Viewer is corrupt for Name Container: "
839 + getTaxonBase().getName().getTitleCache());
840 }
841 return nameViewer;
842 }
843
844 /**
845 * If <code>textViewer</code> has already been set, it will show a
846 * <code>prompt</code> along the lines of
847 * "Click here to start entering data" when empty.
848 *
849 * @param prompt
850 * a {@link java.lang.String} object.
851 */
852 public void createEmptyViewerPrompt(final String prompt) {
853
854 Assert.isNotNull(getNameViewer());
855
856 final StyledText textControl = getNameViewer().getTextWidget();
857 final IDocument document = getNameViewer().getDocument();
858
859 setFocusListener(new FocusListener() {
860
861 public void focusGained(FocusEvent e) {
862 if (document.get().equals(prompt)) {
863 textControl.setFont(getViewerFont());
864 document.set("");
865 }
866 }
867
868 public void focusLost(FocusEvent e) {
869 if (document.getLength() == 0) {
870 initEmptyText();
871 }
872 }
873
874 });
875 textControl.addFocusListener(getFocusListener());
876
877 if (document.getLength() == 0) {
878 textControl.setFont(EditorUtil
879 .getFont(Resources.FONT_DEFAULT_PROMPT));
880 document.set(prompt);
881 }
882 }
883
884 /**
885 * <p>
886 * getViewerFont
887 * </p>
888 *
889 * @return a {@link org.eclipse.swt.graphics.Font} object.
890 */
891 abstract protected Font getViewerFont();
892
893 /**
894 * <p>
895 * initEmptyText
896 * </p>
897 */
898 protected void initEmptyText() {
899 Font defaultFont = EditorUtil.getFont(Resources.FONT_DEFAULT_PROMPT);
900 getNameViewer().getTextWidget().setFont(defaultFont);
901
902 getNameViewer().getDocument().set(getEmptyTextPrompt());
903 placeCursor();
904 }
905
906 /**
907 * <p>
908 * Setter for the field <code>focusListener</code>.
909 * </p>
910 *
911 * @param focusListener
912 * a {@link org.eclipse.swt.events.FocusListener} object.
913 */
914 protected void setFocusListener(FocusListener focusListener) {
915 this.focusListener = focusListener;
916 }
917
918 private FocusListener getFocusListener() {
919 return focusListener;
920 }
921
922 /**
923 * <p>
924 * setDirty
925 * </p>
926 *
927 * @param isDirty
928 * a boolean.
929 */
930 public void setDirty(boolean isDirty) {
931 if (isDirty) {
932 getEditor().getManagedForm().dirtyStateChanged();
933 }
934 this.isDirty = isDirty;
935 }
936
937 /**
938 * <p>
939 * isDirty
940 * </p>
941 *
942 * @return a boolean.
943 */
944 public boolean isDirty() {
945 return isDirty;
946 }
947
948 /**
949 * <p>
950 * setMenu
951 * </p>
952 *
953 * @param menu
954 * a {@link org.eclipse.swt.widgets.Menu} object.
955 */
956 public void setMenu(Menu menu) {
957 control.setMenu(menu);
958
959 getNameViewer().setMenu(menu);
960 }
961
962 private Control[] draggableControls;
963
964 /**
965 * <p>
966 * setDraggableControl
967 * </p>
968 *
969 * @param controls
970 * an array of {@link org.eclipse.swt.widgets.Control} objects.
971 */
972 protected void setDraggableControl(Control[] controls) {
973 draggableControls = controls;
974 }
975
976 /**
977 * <p>
978 * setIsDraggable
979 * </p>
980 *
981 * @param draggable
982 * a boolean.
983 */
984 public void setIsDraggable(boolean draggable) {
985
986 if (draggable) {
987
988 if (draggableControls == null) {
989 throw new NullPointerException(
990 "Draggable controls must be set to add draggability");
991 }
992
993 Transfer[] types = new Transfer[] { CdmDataTransfer.getInstance() };
994 int operations = DND.DROP_MOVE;
995
996 for (Control draggableControl : draggableControls) {
997 DragSource dragSource = new DragSource(draggableControl,
998 operations);
999 dragSource.setTransfer(types);
1000
1001 dragSource.addDragListener(new NameEditorDragListener(this));
1002 dragSource.setDragSourceEffect(new NameEditorDragSourceEffect(
1003 control));
1004 }
1005 }
1006 }
1007
1008 private String nonEditableText;
1009
1010 ControlListener nonEditableResizeListener = new ControlAdapter() {
1011
1012 int width = 0;
1013
1014 @Override
1015 public void controlResized(ControlEvent e) {
1016 if (nonEditableInfoLabel.getBounds().width == width) {
1017 return;
1018 }
1019 width = nonEditableInfoLabel.getBounds().width;
1020 if (nonEditableInfoLabel.getBounds().width > 0) {
1021 nonEditableInfoLabel.setText(Dialog.shortenText(
1022 nonEditableText.toUpperCase(), nonEditableInfoLabel));
1023 }
1024 }
1025 };
1026
1027 private String nonEditableHoverText;
1028
1029 private LabelEllipsisListener nonEditableLabelEllipsisListener;
1030
1031 private T data;
1032
1033 private boolean enabled;
1034
1035 /**
1036 * nonEditableInfo is a label displayed underneath a GroupedComposite's
1037 * input field. For instance, NameComposites display things like name
1038 * relations, sec. references, etc. here.
1039 *
1040 * @param info
1041 * the text to display in the label
1042 * @param append
1043 * whether the string should be appended to text that is already
1044 * shown in the label
1045 */
1046 public void setNonEditableInfo(String info, boolean append) {
1047 // TODO non editable info should only be drawn once, when everything
1048 // else is drawn
1049 info = info.toUpperCase();
1050
1051 if (append) {
1052 nonEditableText += ", " + info;
1053 nonEditableHoverText += "\n" + info;
1054 } else {
1055 nonEditableText = info;
1056 nonEditableHoverText = info;
1057 }
1058
1059 if (nonEditableInfoLabel == null) {
1060 nonEditableInfoLabel = getEditor().getToolkit().createLabel(
1061 control, "");
1062 TableWrapData layoutData = new TableWrapData(
1063 TableWrapData.FILL_GRAB, TableWrapData.TOP);
1064 // Set indent to viewer ruler's width
1065 if (getNameViewer().getRulerControl() != null) {
1066 // TODO right justify
1067 layoutData.indent = NameViewer.RULER_WIDTH;
1068 }
1069 nonEditableInfoLabel.setLayoutData(layoutData);
1070
1071 nonEditableLabelEllipsisListener = new LabelEllipsisListener(
1072 nonEditableInfoLabel) {
1073 @Override
1074 public String getLabelText() {
1075 return nonEditableText.toUpperCase();
1076 }
1077 };
1078 nonEditableInfoLabel
1079 .addControlListener(nonEditableLabelEllipsisListener);
1080
1081 nonEditableInfoHover = new DefaultToolTip(nonEditableInfoLabel);
1082 nonEditableInfoHover.setRespectDisplayBounds(true);
1083
1084 }
1085 nonEditableInfoHover.setText(nonEditableHoverText);
1086 nonEditableInfoLabel.setText(nonEditableText);
1087
1088 calculateAnnotations();
1089 }
1090
1091 /**
1092 * <p>
1093 * Getter for the field <code>data</code>.
1094 * </p>
1095 *
1096 * @return a T object.
1097 */
1098 public T getData() {
1099 return data;
1100 }
1101
1102 /**
1103 * <p>
1104 * Setter for the field <code>data</code>.
1105 * </p>
1106 *
1107 * @param data
1108 * a T object.
1109 */
1110 public void setData(T data) {
1111 this.data = (T) HibernateProxyHelper.deproxy(data);
1112 }
1113
1114 /**
1115 * If the user hitting carriage return should cause something to happen -
1116 * i.e. the creation of a new composite - call this method and override the
1117 * method handleSplitText().
1118 */
1119 protected void createLineBreakListener() {
1120 lineBreakListener = new LineBreakListener() {
1121 @Override
1122 public void handleSplitText(String text) {
1123 AbstractGroupedContainer.this.handleSplitText(text);
1124 }
1125
1126 };
1127
1128 getNameViewer().getTextWidget().addVerifyListener(lineBreakListener);
1129
1130 }
1131
1132 abstract class LabelEllipsisListener extends ControlAdapter {
1133
1134 private final Label label;
1135 int width = 0;
1136
1137 LabelEllipsisListener(Label label) {
1138 this.label = label;
1139 }
1140
1141 abstract public String getLabelText();
1142
1143 @Override
1144 public void controlResized(ControlEvent e) {
1145 if (label.getBounds().width == width) {
1146 return;
1147 }
1148 width = label.getBounds().width;
1149 if (label.getBounds().width > 0) {
1150 label.setText(TextHelper.shortenText(getLabelText(), label));
1151 }
1152 }
1153 }
1154
1155 /**
1156 * <p>
1157 * storeCursor
1158 * </p>
1159 */
1160 public void storeCursor() {
1161 this.cursorPosition = getNameViewer().getCursorPosition();
1162 }
1163
1164 /**
1165 * Puts the cursor to the position it was last seen on or to the end of line
1166 * if no former position is known.
1167 */
1168 public void placeCursor() {
1169 if (cursorPosition == 0) {
1170 getNameViewer().setCursorToEOL();
1171 } else {
1172 getNameViewer().setCursorPosition(cursorPosition);
1173 }
1174 }
1175
1176 /**
1177 * <p>
1178 * Setter for the field <code>group</code>.
1179 * </p>
1180 *
1181 * @param group
1182 * a
1183 * {@link eu.etaxonomy.taxeditor.editor.name.container.AbstractGroup}
1184 * object.
1185 */
1186 public void setGroup(AbstractGroup group) {
1187 this.group = group;
1188 }
1189
1190 /**
1191 * <p>
1192 * restoreColor
1193 * </p>
1194 */
1195 public void restoreColor() {
1196 setBackground(backgroundColor);
1197 }
1198
1199 /*
1200 * (non-Javadoc)
1201 *
1202 * @see
1203 * org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm
1204 * )
1205 */
1206 @Override
1207 public void initialize(IManagedForm form) {
1208 // TODO Auto-generated method stub
1209
1210 }
1211
1212 /*
1213 * (non-Javadoc)
1214 *
1215 * @see org.eclipse.ui.forms.IFormPart#dispose()
1216 */
1217 @Override
1218 public void dispose() {
1219 if (getControl() != null) {
1220 setMenu(null);
1221 getControl().dispose();
1222 }
1223 }
1224
1225 /*
1226 * (non-Javadoc)
1227 *
1228 * @see org.eclipse.ui.forms.IFormPart#commit(boolean)
1229 */
1230 @Override
1231 public void commit(boolean onSave) {
1232 if (isDirty()) {
1233 persistName();
1234 }
1235 }
1236
1237 /*
1238 * (non-Javadoc)
1239 *
1240 * @see org.eclipse.ui.forms.IFormPart#setFormInput(java.lang.Object)
1241 */
1242 @Override
1243 public boolean setFormInput(Object input) {
1244 return false;
1245 }
1246
1247 /*
1248 * (non-Javadoc)
1249 *
1250 * @see org.eclipse.ui.forms.IFormPart#setFocus()
1251 */
1252 @Override
1253 public void setFocus() {
1254 getNameViewer().getControl().setFocus();
1255 }
1256
1257 /*
1258 * (non-Javadoc)
1259 *
1260 * @see org.eclipse.ui.forms.IFormPart#isStale()
1261 */
1262 @Override
1263 public boolean isStale() {
1264 return false;
1265 }
1266
1267 public void setDisabled(boolean disabled) {
1268 this.enabled = !disabled;
1269 setEnabled(enabled);
1270 }
1271
1272 public void setEnabled(boolean enabled) {
1273 this.enabled = enabled;
1274 Color color = enabled ? control.getForeground() : EditorUtil.getColor(Resources.COLOR_DISABLED_EDITOR);
1275
1276 getNameViewer().getTextWidget().setEditable(enabled);
1277 getNameViewer().getTextWidget().setEnabled(enabled);
1278 getNameViewer().getTextWidget().setForeground(color);
1279 }
1280
1281 public boolean isEnabled(){
1282 return enabled;
1283 }
1284 }