2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.taxeditor
.editor
;
12 import org
.apache
.log4j
.Logger
;
13 import org
.eclipse
.core
.runtime
.Assert
;
14 import org
.eclipse
.jface
.dialogs
.Dialog
;
15 import org
.eclipse
.jface
.text
.IDocument
;
16 import org
.eclipse
.jface
.text
.TextViewer
;
17 import org
.eclipse
.jface
.window
.DefaultToolTip
;
18 import org
.eclipse
.swt
.SWT
;
19 import org
.eclipse
.swt
.custom
.StyledText
;
20 import org
.eclipse
.swt
.dnd
.DND
;
21 import org
.eclipse
.swt
.dnd
.DragSource
;
22 import org
.eclipse
.swt
.dnd
.DragSourceAdapter
;
23 import org
.eclipse
.swt
.dnd
.DragSourceEvent
;
24 import org
.eclipse
.swt
.dnd
.DragSourceListener
;
25 import org
.eclipse
.swt
.dnd
.Transfer
;
26 import org
.eclipse
.swt
.events
.ControlAdapter
;
27 import org
.eclipse
.swt
.events
.ControlEvent
;
28 import org
.eclipse
.swt
.events
.ControlListener
;
29 import org
.eclipse
.swt
.events
.FocusAdapter
;
30 import org
.eclipse
.swt
.events
.FocusEvent
;
31 import org
.eclipse
.swt
.events
.FocusListener
;
32 import org
.eclipse
.swt
.events
.MouseAdapter
;
33 import org
.eclipse
.swt
.events
.MouseEvent
;
34 import org
.eclipse
.swt
.graphics
.Color
;
35 import org
.eclipse
.swt
.graphics
.Font
;
36 import org
.eclipse
.swt
.graphics
.GC
;
37 import org
.eclipse
.swt
.graphics
.Image
;
38 import org
.eclipse
.swt
.widgets
.Composite
;
39 import org
.eclipse
.swt
.widgets
.Control
;
40 import org
.eclipse
.swt
.widgets
.Label
;
41 import org
.eclipse
.swt
.widgets
.Menu
;
42 import org
.eclipse
.ui
.forms
.IManagedForm
;
43 import org
.eclipse
.ui
.forms
.widgets
.TableWrapData
;
44 import org
.eclipse
.ui
.forms
.widgets
.TableWrapLayout
;
46 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
47 import eu
.etaxonomy
.taxeditor
.editor
.name
.NameViewer
;
48 import eu
.etaxonomy
.taxeditor
.store
.model
.Resources
;
51 * Formats <code>GroupedComposite</code> with cosmetic and layout properties specific to the
52 * Editor. This should be used to maintain a consistent look and feel for all Editor
53 * freetext area components, such as DescriptionElementComposite.
55 * Requires an <code>IManagedForm</code>, whose <code>input</code> is set to the contents
56 * of {@link #getData()} when the <code>GroupedComposite</code> gets focus, i.e. to
57 * populate the property sheet with the data.
60 * The <code>IManagedForm</code> is also required to have a <code>Taxon</code> in its
61 * own <code>getData()</code>.
64 * The <code>IManagedForm</code> can also used for drawing borders by calling the method
65 * <code>createBorderSupport()</code>.
71 abstract public class GroupedComposite
extends Composite
implements IHasPropertySource
{
72 private static final Logger logger
= Logger
.getLogger(GroupedComposite
.class);
74 protected AbstractTaxonEditor editor
;
76 protected NameViewer textViewer
;
77 protected IManagedForm managedForm
;
78 private Label nonEditableInfoLabel
;
79 private DefaultToolTip nonEditableInfoHover
;
81 private Color defaultGroupBackgroundColor
= Resources
.getColor(Resources
.COLOR_COMPOSITE_BACKGROUND
);
83 protected Taxon taxon
;
85 private CompositeBorderDecorator borderDecorator
;
86 private FocusListener focusListener
;
87 private LineBreakListener lineBreakListener
;
88 private ParseListener parseListener
;
90 private String emptyViewerPrompt
;
92 public GroupedComposite(AbstractTaxonEditor editor
, Composite parent
){
93 super(parent
, SWT
.NONE
);
96 this.managedForm
= editor
.getManagedForm();
98 Object formData
= managedForm
.getForm().getBody().getData();
99 Assert
.isTrue(formData
instanceof Taxon
,
100 "Managed form must have a Taxon in its data field.");
101 taxon
= (Taxon
) formData
;
106 protected void createControl() {
107 setLayoutData(new TableWrapData(TableWrapData
.FILL_GRAB
));
108 TableWrapLayout layout
= new TableWrapLayout();
109 layout
.leftMargin
= 0;
110 layout
.topMargin
= 0;
111 layout
.bottomMargin
= 0;
112 layout
.verticalSpacing
= 0;
115 setBackground(defaultGroupBackgroundColor
);
118 protected void createLineWrapSupport() {
119 if (textViewer
instanceof NameViewer
) {
120 new LineWrapSupport(textViewer
, managedForm
);
122 logger
.warn("Can't create line wrap support because textViewer has not been initialized.");
126 public Taxon
getTaxon() {
130 protected void createTextViewer() {
131 textViewer
= new NameViewer(this);
133 focusListener
= new FocusAdapter() {
134 public void focusGained(FocusEvent e
) {
139 textViewer
.getTextWidget().addFocusListener(focusListener
);
141 MouseAdapter mouseListener
= new MouseAdapter() {
142 public void mouseDown(MouseEvent e
) {
146 this.addMouseListener(mouseListener
);
147 textViewer
.getRulerControl().addMouseListener(mouseListener
);
153 public void setIcon(Image icon
) {
154 if (textViewer
instanceof NameViewer
) {
155 textViewer
.setIcon(icon
);
157 logger
.warn("Can't set icon because textViewer has not been initialized.");
164 public void setIndent(int indent
) {
165 if (getLayout() instanceof TableWrapLayout
) {
166 TableWrapLayout layout
= ((TableWrapLayout
) getLayout());
167 layout
.leftMargin
= indent
;
168 this.setLayout(layout
);
170 logger
.warn("Couldn't indent - composite's layout must be TableWrapLayout.");
175 * @see org.eclipse.swt.widgets.Composite#setFocus()
177 public boolean setFocus() {
183 * Set background on selection and the cursor to EOL
184 * I wanted to move this to setFocus() but that leads to an infinite loop
186 public void setSelected(){
187 setBackground(Resources
.getColor(Resources
.COLOR_COMPOSITE_SELECTED
));
188 if(textViewer
!= null){
189 textViewer
.setCursorToEOL();
196 protected void setSelection() {
197 managedForm
.setInput(this);
204 public void setBackground(Color color
) {
205 super.setBackground(color
);
206 if(textViewer
!= null){
207 textViewer
.setBackground(color
);
212 * @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
214 public void setFont(Font font
) {
215 if (textViewer
!= null) {
216 textViewer
.getTextWidget().setFont(font
);
218 logger
.warn("Can't set font because textViewer has not been initalized.");
222 public TextViewer
getTextViewer() {
227 * If <code>textViewer</code> has already been set, it will show a
228 * <code>prompt</code> along the lines of "Click here to start entering data"
233 public void createEmptyViewerPrompt(final String prompt
) {
235 emptyViewerPrompt
= prompt
;
237 Assert
.isNotNull(textViewer
);
238 // new EmptyTextViewerPrompt(getTextViewer(), prompt);
240 final StyledText textControl
= textViewer
.getTextWidget();
241 final IDocument document
= textViewer
.getDocument();
242 final Font promptFont
= Resources
243 .getFont(Resources
.FONT_DEFAULT_PROMPT
);
244 setFocusListener(new FocusListener() {
247 public void focusGained(FocusEvent e
) {
248 if (document
.get().equals(prompt
)) {
249 textControl
.setFont(getViewerFont());
255 public void focusLost(FocusEvent e
) {
256 if (document
.getLength() == 0) {
262 textControl
.addFocusListener(getFocusListener());
264 if (document
.getLength() == 0) {
265 textControl
.setFont(promptFont
);
266 document
.set(prompt
);
270 abstract protected Font
getViewerFont();
272 protected void initEmptyText() {
273 textViewer
.getTextWidget().setFont(
275 .getFont(Resources
.FONT_DEFAULT_PROMPT
));
277 textViewer
.getDocument().set(getEmptyTextPrompt());
278 textViewer
.setCursorToEOL();
281 protected String
getEmptyTextPrompt() {
282 if (emptyViewerPrompt
== null) {
283 emptyViewerPrompt
= "Click to edit";
285 return emptyViewerPrompt
;
288 private void setFocusListener(FocusListener focusListener
) {
289 this.focusListener
= focusListener
;
292 private FocusListener
getFocusListener() {
293 return focusListener
;
299 public void createBorderSupport() {
301 if (textViewer
== null) {
302 logger
.warn("Could not create border support - getTextViewer() returned null.");
304 borderDecorator
= new CompositeBorderDecorator(
305 textViewer
.getTextWidget(), managedForm
);
306 borderDecorator
.setLoseFocus(false);
307 textViewer
.getTextWidget().addFocusListener(borderDecorator
);
311 protected void setBorderDecorator(CompositeBorderDecorator borderDecorator
) {
312 this.borderDecorator
= borderDecorator
;
315 protected CompositeBorderDecorator
getBorderDecorator() {
316 return borderDecorator
;
319 public void drawBorder() {
320 if (borderDecorator
!= null) {
321 borderDecorator
.paintBorder();
328 protected void setDirty(boolean isDirty
) {
329 managedForm
.dirtyStateChanged();
332 public void setMenu (Menu menu
) {
335 if (textViewer
!= null) {
336 textViewer
.getRulerControl().setMenu(menu
);
337 textViewer
.getTextWidget().setMenu(menu
);
341 public void unpaintBorder() {
342 if (borderDecorator
!= null) {
343 borderDecorator
.unpaintBorder();
347 private Control draggableControl
;
348 private DragSource dragSource
;
350 protected void setDraggableControl(Control control
) {
351 draggableControl
= control
;
354 public DragSource
getDragSource() {
358 public void setIsDraggable(boolean draggable
) {
362 if (dragSource
!= null) {
363 // Already initialized
367 if (draggableControl
== null) {
368 throw new NullPointerException(
369 "Draggable control must be set to add draggability");
372 Transfer
[] types
= new Transfer
[] { WidgetTransfer
.getInstance() };
373 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
375 dragSource
= new DragSource(draggableControl
, operations
);
376 dragSource
.setTransfer(types
);
377 dragSource
.addDragListener(dragSourceListener
);
385 * Drag listener which passes the Composite as the data in a drag event.
387 DragSourceListener dragSourceListener
= new DragSourceAdapter() {
389 public void dragStart(DragSourceEvent event
) {
390 if (textViewer
!= null) {
391 textViewer
.getTextWidget().setFocus();
396 public void dragSetData(DragSourceEvent event
) {
397 WidgetTransfer
.getInstance().setWidget(GroupedComposite
.this);
401 private String nonEditableText
;
402 ControlListener nonEditableResizeListener
= new ControlAdapter() {
406 public void controlResized(ControlEvent e
) {
407 if (nonEditableInfoLabel
.getBounds().width
== width
) {
410 width
= nonEditableInfoLabel
.getBounds().width
;
411 if (nonEditableInfoLabel
.getBounds().width
> 0) {
412 nonEditableInfoLabel
.setText(
413 Dialog
.shortenText(nonEditableText
.toUpperCase(),
414 nonEditableInfoLabel
));
419 private String nonEditableHoverText
;
421 private LabelEllipsisListener nonEditableLabelEllipsisListener
;
424 * nonEditableInfo is a label displayed underneath a GroupedComposite's
425 * input field. For instance, NameComposites display things like name relations,
426 * sec. references, etc. here.
430 public void setNonEditableInfo(String info
) {
431 // TODO non editable info should only be drawn once, when everything else is drawn
432 info
= info
.toUpperCase();
433 if (nonEditableInfoLabel
== null) {
434 nonEditableText
= info
;
435 nonEditableHoverText
= info
;
437 nonEditableInfoLabel
= new Label(this, SWT
.NONE
);
438 TableWrapData layoutData
= new TableWrapData(TableWrapData
.FILL_GRAB
, TableWrapData
.TOP
);
439 // Set indent to viewer ruler's width
440 if (textViewer
!= null && textViewer
.getRulerControl() != null) {
441 // TODO right justify
442 layoutData
.indent
= NameViewer
.RULER_WIDTH
;
444 nonEditableInfoLabel
.setLayoutData(layoutData
);
448 nonEditableLabelEllipsisListener
= new LabelEllipsisListener(nonEditableInfoLabel
) {
450 public String
getLabelText() {
451 return nonEditableText
.toUpperCase();
454 nonEditableInfoLabel
.addControlListener(nonEditableLabelEllipsisListener
);
456 nonEditableInfoHover
= new DefaultToolTip(nonEditableInfoLabel
);
457 nonEditableInfoHover
.setRespectDisplayBounds(true);
460 nonEditableText
+= ", " + info
;
461 nonEditableHoverText
+= "\n" + info
;
464 nonEditableInfoHover
.setText(nonEditableHoverText
);
468 * If the user entering text requires parsing, call this method and override
469 * the method parse().
471 protected void createParser() {
472 if (textViewer
!= null) {
473 parseListener
= new ParseListener() {
475 public void parse(String text
) {
476 GroupedComposite
.this.parse(text
);
479 textViewer
.getTextWidget().addModifyListener(parseListener
);
481 logger
.warn("Can't create parser because textViewer has not been initalized.");
486 * Supposed to be overridden in implementing classes
490 protected void parse(String text
) {
491 logger
.warn("No parse method defined for this composite.");
495 * If the user hitting carriage return should cause something to happen -
496 * i.e. the creation of a new composite - call this method and override
497 * the method handleSplitText().
499 protected void createLineBreakListener() {
500 if (textViewer
!= null) {
501 lineBreakListener
= new LineBreakListener() {
503 public void handleSplitText(String text
) {
504 GroupedComposite
.this.handleSplitText(text
);
508 textViewer
.getTextWidget().addVerifyListener(lineBreakListener
);
509 textViewer
.getTextWidget().addKeyListener(lineBreakListener
);
511 logger
.warn("Can't create line break listener because textViewer has not been initalized.");
516 * Supposed to be overridden in implementing classes
520 protected void handleSplitText(String text
) {
521 logger
.warn("No handleSplitText method defined for this composite.");
525 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
527 public void dispose () {
529 if (dragSource
!= null) {
530 dragSource
.removeDragListener(dragSourceListener
);
533 if (textViewer
!= null) {
534 StyledText textWidget
= textViewer
.getTextWidget();
536 if (focusListener
!= null) {
537 textWidget
.removeFocusListener(focusListener
);
540 if (parseListener
!= null) {
541 textWidget
.removeModifyListener(parseListener
);
544 if (lineBreakListener
!= null) {
545 textWidget
.removeVerifyListener(lineBreakListener
);
546 textWidget
.removeKeyListener(lineBreakListener
);
549 if (borderDecorator
!= null) {
550 textWidget
.removeFocusListener(borderDecorator
);
554 if (nonEditableInfoLabel
!= null && nonEditableLabelEllipsisListener
!= null) {
555 nonEditableInfoLabel
.removeControlListener(nonEditableLabelEllipsisListener
);
561 public static final String ELLIPSIS
= "...";
567 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
569 public static String
shortenText(String textValue
, Control control
) {
570 if (textValue
== null) {
573 GC gc
= new GC(control
);
574 int maxWidth
= control
.getBounds().width
;
575 int maxExtent
= gc
.textExtent(textValue
).x
;
576 if (maxExtent
< maxWidth
) {
580 int length
= textValue
.length();
581 int charsToClip
= Math
.round(0.95f
*length
* (1 - ((float)maxWidth
/maxExtent
)));
583 int end
= length
- charsToClip
;
585 String s1
= textValue
.substring(0, end
);
586 String s
= s1
+ ELLIPSIS
;
587 int l
= gc
.textExtent(s
).x
;
599 abstract class LabelEllipsisListener
extends ControlAdapter
{
604 LabelEllipsisListener(Label label
) {
608 abstract public String
getLabelText();
610 public void controlResized(ControlEvent e
) {
611 if (label
.getBounds().width
== width
) {
614 width
= label
.getBounds().width
;
615 if (label
.getBounds().width
> 0) {
617 // Dialog.shortenText(getLabelText(), label));
618 shortenText(getLabelText(), label
));