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
;
105 logger
.trace(this.getClass().getSimpleName() + " created");
108 protected void createControl() {
109 setLayoutData(new TableWrapData(TableWrapData
.FILL_GRAB
));
110 TableWrapLayout layout
= new TableWrapLayout();
111 layout
.leftMargin
= 0;
112 layout
.topMargin
= 0;
113 layout
.bottomMargin
= 0;
114 layout
.verticalSpacing
= 0;
117 setBackground(defaultGroupBackgroundColor
);
120 protected void createLineWrapSupport() {
121 if (textViewer
instanceof NameViewer
) {
122 new LineWrapSupport(textViewer
, managedForm
);
124 logger
.warn("Can't create line wrap support because textViewer has not been initialized.");
128 public Taxon
getTaxon() {
132 protected void createTextViewer() {
133 textViewer
= new NameViewer(this);
135 focusListener
= new FocusAdapter() {
136 public void focusGained(FocusEvent e
) {
141 textViewer
.getTextWidget().addFocusListener(focusListener
);
143 MouseAdapter mouseListener
= new MouseAdapter() {
144 public void mouseDown(MouseEvent e
) {
148 this.addMouseListener(mouseListener
);
149 textViewer
.getRulerControl().addMouseListener(mouseListener
);
155 public void setIcon(Image icon
) {
156 if (textViewer
instanceof NameViewer
) {
157 textViewer
.setIcon(icon
);
159 logger
.warn("Can't set icon because textViewer has not been initialized.");
166 public void setIndent(int indent
) {
167 if (getLayout() instanceof TableWrapLayout
) {
168 TableWrapLayout layout
= ((TableWrapLayout
) getLayout());
169 layout
.leftMargin
= indent
;
170 this.setLayout(layout
);
172 logger
.warn("Couldn't indent - composite's layout must be TableWrapLayout.");
177 * @see org.eclipse.swt.widgets.Composite#setFocus()
179 public boolean setFocus() {
185 * Set background on selection and the cursor to EOL
186 * I wanted to move this to setFocus() but that leads to an infinite loop
188 public void setSelected(){
189 setBackground(Resources
.getColor(Resources
.COLOR_COMPOSITE_SELECTED
));
190 if(textViewer
!= null){
191 textViewer
.setCursorToEOL();
198 protected void setSelection() {
199 managedForm
.setInput(this);
206 public void setBackground(Color color
) {
207 super.setBackground(color
);
208 if(textViewer
!= null){
209 textViewer
.setBackground(color
);
214 * @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
216 public void setFont(Font font
) {
217 if (textViewer
!= null) {
218 textViewer
.getTextWidget().setFont(font
);
220 logger
.warn("Can't set font because textViewer has not been initalized.");
224 public TextViewer
getTextViewer() {
229 * If <code>textViewer</code> has already been set, it will show a
230 * <code>prompt</code> along the lines of "Click here to start entering data"
235 public void createEmptyViewerPrompt(final String prompt
) {
237 emptyViewerPrompt
= prompt
;
239 Assert
.isNotNull(textViewer
);
240 // new EmptyTextViewerPrompt(getTextViewer(), prompt);
242 final StyledText textControl
= textViewer
.getTextWidget();
243 final IDocument document
= textViewer
.getDocument();
244 final Font promptFont
= Resources
245 .getFont(Resources
.FONT_DEFAULT_PROMPT
);
246 setFocusListener(new FocusListener() {
249 public void focusGained(FocusEvent e
) {
250 if (document
.get().equals(prompt
)) {
251 textControl
.setFont(getViewerFont());
257 public void focusLost(FocusEvent e
) {
258 if (document
.getLength() == 0) {
264 textControl
.addFocusListener(getFocusListener());
266 if (document
.getLength() == 0) {
267 textControl
.setFont(promptFont
);
268 document
.set(prompt
);
272 abstract protected Font
getViewerFont();
274 protected void initEmptyText() {
275 textViewer
.getTextWidget().setFont(
277 .getFont(Resources
.FONT_DEFAULT_PROMPT
));
279 textViewer
.getDocument().set(getEmptyTextPrompt());
280 textViewer
.setCursorToEOL();
283 protected String
getEmptyTextPrompt() {
284 if (emptyViewerPrompt
== null) {
285 emptyViewerPrompt
= "Click to edit";
287 return emptyViewerPrompt
;
290 private void setFocusListener(FocusListener focusListener
) {
291 this.focusListener
= focusListener
;
294 private FocusListener
getFocusListener() {
295 return focusListener
;
301 public void createBorderSupport() {
303 if (textViewer
== null) {
304 logger
.warn("Could not create border support - getTextViewer() returned null.");
306 borderDecorator
= new CompositeBorderDecorator(
307 textViewer
.getTextWidget(), managedForm
);
308 borderDecorator
.setLoseFocus(false);
309 textViewer
.getTextWidget().addFocusListener(borderDecorator
);
313 protected void setBorderDecorator(CompositeBorderDecorator borderDecorator
) {
314 this.borderDecorator
= borderDecorator
;
317 protected CompositeBorderDecorator
getBorderDecorator() {
318 return borderDecorator
;
321 public void drawBorder() {
322 if (borderDecorator
!= null) {
323 borderDecorator
.paintBorder();
330 protected void setDirty(boolean isDirty
) {
331 managedForm
.dirtyStateChanged();
334 public void setMenu (Menu menu
) {
337 if (textViewer
!= null) {
338 textViewer
.getRulerControl().setMenu(menu
);
339 textViewer
.getTextWidget().setMenu(menu
);
343 public void unpaintBorder() {
344 if (borderDecorator
!= null) {
345 borderDecorator
.unpaintBorder();
349 private Control draggableControl
;
350 private DragSource dragSource
;
352 protected void setDraggableControl(Control control
) {
353 draggableControl
= control
;
356 public DragSource
getDragSource() {
360 public void setIsDraggable(boolean draggable
) {
364 if (dragSource
!= null) {
365 // Already initialized
369 if (draggableControl
== null) {
370 throw new NullPointerException(
371 "Draggable control must be set to add draggability");
374 Transfer
[] types
= new Transfer
[] { WidgetTransfer
.getInstance() };
375 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
377 dragSource
= new DragSource(draggableControl
, operations
);
378 dragSource
.setTransfer(types
);
379 dragSource
.addDragListener(dragSourceListener
);
387 * Drag listener which passes the Composite as the data in a drag event.
389 DragSourceListener dragSourceListener
= new DragSourceAdapter() {
391 public void dragStart(DragSourceEvent event
) {
392 if (textViewer
!= null) {
393 textViewer
.getTextWidget().setFocus();
398 public void dragSetData(DragSourceEvent event
) {
399 WidgetTransfer
.getInstance().setWidget(GroupedComposite
.this);
403 private String nonEditableText
;
404 ControlListener nonEditableResizeListener
= new ControlAdapter() {
408 public void controlResized(ControlEvent e
) {
409 if (nonEditableInfoLabel
.getBounds().width
== width
) {
412 width
= nonEditableInfoLabel
.getBounds().width
;
413 if (nonEditableInfoLabel
.getBounds().width
> 0) {
414 nonEditableInfoLabel
.setText(
415 Dialog
.shortenText(nonEditableText
.toUpperCase(),
416 nonEditableInfoLabel
));
421 private String nonEditableHoverText
;
423 private LabelEllipsisListener nonEditableLabelEllipsisListener
;
426 * nonEditableInfo is a label displayed underneath a GroupedComposite's
427 * input field. For instance, NameComposites display things like name relations,
428 * sec. references, etc. here.
432 public void setNonEditableInfo(String info
) {
433 // TODO non editable info should only be drawn once, when everything else is drawn
434 info
= info
.toUpperCase();
435 if (nonEditableInfoLabel
== null) {
436 nonEditableText
= info
;
437 nonEditableHoverText
= info
;
439 nonEditableInfoLabel
= new Label(this, SWT
.NONE
);
440 TableWrapData layoutData
= new TableWrapData(TableWrapData
.FILL_GRAB
, TableWrapData
.TOP
);
441 // Set indent to viewer ruler's width
442 if (textViewer
!= null && textViewer
.getRulerControl() != null) {
443 // TODO right justify
444 layoutData
.indent
= NameViewer
.RULER_WIDTH
;
446 nonEditableInfoLabel
.setLayoutData(layoutData
);
450 nonEditableLabelEllipsisListener
= new LabelEllipsisListener(nonEditableInfoLabel
) {
452 public String
getLabelText() {
453 return nonEditableText
.toUpperCase();
456 nonEditableInfoLabel
.addControlListener(nonEditableLabelEllipsisListener
);
458 nonEditableInfoHover
= new DefaultToolTip(nonEditableInfoLabel
);
459 nonEditableInfoHover
.setRespectDisplayBounds(true);
462 nonEditableText
+= ", " + info
;
463 nonEditableHoverText
+= "\n" + info
;
466 nonEditableInfoHover
.setText(nonEditableHoverText
);
470 * If the user entering text requires parsing, call this method and override
471 * the method parse().
473 protected void createParser() {
474 if (textViewer
!= null) {
475 parseListener
= new ParseListener() {
477 public void parse(String text
) {
478 GroupedComposite
.this.parse(text
);
481 textViewer
.getTextWidget().addModifyListener(parseListener
);
483 logger
.warn("Can't create parser because textViewer has not been initalized.");
488 * Supposed to be overridden in implementing classes
492 protected void parse(String text
) {
493 logger
.warn("No parse method defined for this composite.");
497 * If the user hitting carriage return should cause something to happen -
498 * i.e. the creation of a new composite - call this method and override
499 * the method handleSplitText().
501 protected void createLineBreakListener() {
502 if (textViewer
!= null) {
503 lineBreakListener
= new LineBreakListener() {
505 public void handleSplitText(String text
) {
506 GroupedComposite
.this.handleSplitText(text
);
510 textViewer
.getTextWidget().addVerifyListener(lineBreakListener
);
511 textViewer
.getTextWidget().addKeyListener(lineBreakListener
);
513 logger
.warn("Can't create line break listener because textViewer has not been initalized.");
518 * Supposed to be overridden in implementing classes
522 protected void handleSplitText(String text
) {
523 logger
.warn("No handleSplitText method defined for this composite.");
527 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
529 public void dispose () {
531 if (dragSource
!= null) {
532 dragSource
.removeDragListener(dragSourceListener
);
535 if (textViewer
!= null) {
536 StyledText textWidget
= textViewer
.getTextWidget();
538 if (focusListener
!= null) {
539 textWidget
.removeFocusListener(focusListener
);
542 if (parseListener
!= null) {
543 textWidget
.removeModifyListener(parseListener
);
546 if (lineBreakListener
!= null) {
547 textWidget
.removeVerifyListener(lineBreakListener
);
548 textWidget
.removeKeyListener(lineBreakListener
);
551 if (borderDecorator
!= null) {
552 textWidget
.removeFocusListener(borderDecorator
);
556 if (nonEditableInfoLabel
!= null && nonEditableLabelEllipsisListener
!= null) {
557 nonEditableInfoLabel
.removeControlListener(nonEditableLabelEllipsisListener
);
563 public static final String ELLIPSIS
= "...";
569 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
571 public static String
shortenText(String textValue
, Control control
) {
572 if (textValue
== null) {
575 GC gc
= new GC(control
);
576 int maxWidth
= control
.getBounds().width
;
577 int maxExtent
= gc
.textExtent(textValue
).x
;
578 if (maxExtent
< maxWidth
) {
582 int length
= textValue
.length();
583 int charsToClip
= Math
.round(0.95f
*length
* (1 - ((float)maxWidth
/maxExtent
)));
585 int end
= length
- charsToClip
;
587 String s1
= textValue
.substring(0, end
);
588 String s
= s1
+ ELLIPSIS
;
589 int l
= gc
.textExtent(s
).x
;
601 abstract class LabelEllipsisListener
extends ControlAdapter
{
606 LabelEllipsisListener(Label label
) {
610 abstract public String
getLabelText();
612 public void controlResized(ControlEvent e
) {
613 if (label
.getBounds().width
== width
) {
616 width
= label
.getBounds().width
;
617 if (label
.getBounds().width
> 0) {
619 // Dialog.shortenText(getLabelText(), label));
620 shortenText(getLabelText(), label
));