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
.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
) {
142 textViewer
.getTextWidget().addFocusListener(focusListener
);
144 MouseAdapter mouseListener
= new MouseAdapter() {
145 public void mouseDown(MouseEvent e
) {
149 this.addMouseListener(mouseListener
);
150 textViewer
.getRulerControl().addMouseListener(mouseListener
);
156 public void setIcon(Image icon
) {
157 if (textViewer
instanceof NameViewer
) {
158 textViewer
.setIcon(icon
);
160 logger
.warn("Can't set icon because textViewer has not been initialized.");
167 public void setIndent(int indent
) {
168 if (getLayout() instanceof TableWrapLayout
) {
169 TableWrapLayout layout
= ((TableWrapLayout
) getLayout());
170 layout
.leftMargin
= indent
;
171 this.setLayout(layout
);
173 logger
.warn("Couldn't indent - composite's layout must be TableWrapLayout.");
178 * @see org.eclipse.swt.widgets.Composite#setFocus()
180 public boolean setFocus() {
186 * Set background on selection and the cursor to EOL
187 * I wanted to move this to setFocus() but that leads to an infinite loop
189 public void setSelected(){
190 setBackground(Resources
.getColor(Resources
.COLOR_COMPOSITE_SELECTED
));
191 if(textViewer
!= null){
192 textViewer
.setCursorToEOL();
199 protected void setSelection() {
200 managedForm
.setInput(this);
207 public void setBackground(Color color
) {
208 super.setBackground(color
);
209 if(textViewer
!= null){
210 textViewer
.setBackground(color
);
215 * @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
217 public void setFont(Font font
) {
218 if (textViewer
!= null) {
219 textViewer
.getTextWidget().setFont(font
);
221 logger
.warn("Can't set font because textViewer has not been initalized.");
225 public TextViewer
getTextViewer() {
230 * If <code>textViewer</code> has already been set, it will show a
231 * <code>prompt</code> along the lines of "Click here to start entering data"
236 public void createEmptyViewerPrompt(final String prompt
) {
238 emptyViewerPrompt
= prompt
;
240 Assert
.isNotNull(textViewer
);
241 // new EmptyTextViewerPrompt(getTextViewer(), prompt);
243 final StyledText textControl
= textViewer
.getTextWidget();
244 final IDocument document
= textViewer
.getDocument();
245 final Font promptFont
= Resources
246 .getFont(Resources
.FONT_DEFAULT_PROMPT
);
247 setFocusListener(new FocusListener() {
250 public void focusGained(FocusEvent e
) {
251 if (document
.get().equals(prompt
)) {
252 textControl
.setFont(getViewerFont());
258 public void focusLost(FocusEvent e
) {
259 if (document
.getLength() == 0) {
265 textControl
.addFocusListener(getFocusListener());
267 if (document
.getLength() == 0) {
268 textControl
.setFont(promptFont
);
269 document
.set(prompt
);
273 abstract protected Font
getViewerFont();
275 protected void initEmptyText() {
276 textViewer
.getTextWidget().setFont(
278 .getFont(Resources
.FONT_DEFAULT_PROMPT
));
280 textViewer
.getDocument().set(getEmptyTextPrompt());
281 textViewer
.setCursorToEOL();
284 protected String
getEmptyTextPrompt() {
285 if (emptyViewerPrompt
== null) {
286 emptyViewerPrompt
= "Click to edit";
288 return emptyViewerPrompt
;
291 private void setFocusListener(FocusListener focusListener
) {
292 this.focusListener
= focusListener
;
295 private FocusListener
getFocusListener() {
296 return focusListener
;
302 public void createBorderSupport() {
304 if (textViewer
== null) {
305 logger
.warn("Could not create border support - getTextViewer() returned null.");
307 borderDecorator
= new CompositeBorderDecorator(
308 textViewer
.getTextWidget(), managedForm
);
309 borderDecorator
.setLoseFocus(false);
310 textViewer
.getTextWidget().addFocusListener(borderDecorator
);
314 protected void setBorderDecorator(CompositeBorderDecorator borderDecorator
) {
315 this.borderDecorator
= borderDecorator
;
318 protected CompositeBorderDecorator
getBorderDecorator() {
319 return borderDecorator
;
322 public void drawBorder() {
323 if (borderDecorator
!= null) {
324 borderDecorator
.paintBorder();
331 protected void setDirty(boolean isDirty
) {
332 managedForm
.dirtyStateChanged();
335 public void setMenu (Menu menu
) {
338 if (textViewer
!= null) {
339 textViewer
.getRulerControl().setMenu(menu
);
340 textViewer
.getTextWidget().setMenu(menu
);
344 public void unpaintBorder() {
345 if (borderDecorator
!= null) {
346 borderDecorator
.unpaintBorder();
350 private Control draggableControl
;
351 private DragSource dragSource
;
353 protected void setDraggableControl(Control control
) {
354 draggableControl
= control
;
357 public DragSource
getDragSource() {
361 public void setIsDraggable(boolean draggable
) {
365 if (dragSource
!= null) {
366 // Already initialized
370 if (draggableControl
== null) {
371 throw new NullPointerException(
372 "Draggable control must be set to add draggability");
375 Transfer
[] types
= new Transfer
[] { WidgetTransfer
.getInstance() };
376 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
378 dragSource
= new DragSource(draggableControl
, operations
);
379 dragSource
.setTransfer(types
);
380 dragSource
.addDragListener(dragSourceListener
);
388 * Drag listener which passes the Composite as the data in a drag event.
390 DragSourceListener dragSourceListener
= new DragSourceAdapter() {
392 public void dragStart(DragSourceEvent event
) {
393 if (textViewer
!= null) {
394 textViewer
.getTextWidget().setFocus();
399 public void dragSetData(DragSourceEvent event
) {
400 WidgetTransfer
.getInstance().setWidget(GroupedComposite
.this);
404 private String nonEditableText
;
405 ControlListener nonEditableResizeListener
= new ControlAdapter() {
409 public void controlResized(ControlEvent e
) {
410 if (nonEditableInfoLabel
.getBounds().width
== width
) {
413 width
= nonEditableInfoLabel
.getBounds().width
;
414 if (nonEditableInfoLabel
.getBounds().width
> 0) {
415 nonEditableInfoLabel
.setText(
416 Dialog
.shortenText(nonEditableText
.toUpperCase(),
417 nonEditableInfoLabel
));
422 private String nonEditableHoverText
;
424 private LabelEllipsisListener nonEditableLabelEllipsisListener
;
427 * nonEditableInfo is a label displayed underneath a GroupedComposite's
428 * input field. For instance, NameComposites display things like name relations,
429 * sec. references, etc. here.
433 public void setNonEditableInfo(String info
) {
434 // TODO non editable info should only be drawn once, when everything else is drawn
435 info
= info
.toUpperCase();
436 if (nonEditableInfoLabel
== null) {
437 nonEditableText
= info
;
438 nonEditableHoverText
= info
;
440 nonEditableInfoLabel
= new Label(this, SWT
.NONE
);
441 TableWrapData layoutData
= new TableWrapData(TableWrapData
.FILL_GRAB
, TableWrapData
.TOP
);
442 // Set indent to viewer ruler's width
443 if (textViewer
!= null && textViewer
.getRulerControl() != null) {
444 // TODO right justify
445 layoutData
.indent
= NameViewer
.RULER_WIDTH
;
447 nonEditableInfoLabel
.setLayoutData(layoutData
);
451 nonEditableLabelEllipsisListener
= new LabelEllipsisListener(nonEditableInfoLabel
) {
453 public String
getLabelText() {
454 return nonEditableText
.toUpperCase();
457 nonEditableInfoLabel
.addControlListener(nonEditableLabelEllipsisListener
);
459 nonEditableInfoHover
= new DefaultToolTip(nonEditableInfoLabel
);
460 nonEditableInfoHover
.setRespectDisplayBounds(true);
463 nonEditableText
+= ", " + info
;
464 nonEditableHoverText
+= "\n" + info
;
467 nonEditableInfoHover
.setText(nonEditableHoverText
);
471 * If the user entering text requires parsing, call this method and override
472 * the method parse().
474 protected void createParser() {
475 if (textViewer
!= null) {
476 parseListener
= new ParseListener() {
478 public void parse(String text
) {
479 GroupedComposite
.this.parse(text
);
482 textViewer
.getTextWidget().addModifyListener(parseListener
);
485 logger
.warn("Can't create parser because textViewer has not been initalized.");
490 * Implement in derived classes
494 protected abstract void parse(String text
);
498 * If the user hitting carriage return should cause something to happen -
499 * i.e. the creation of a new composite - call this method and override
500 * the method handleSplitText().
502 protected void createLineBreakListener() {
503 if (textViewer
!= null) {
504 lineBreakListener
= new LineBreakListener() {
506 public void handleSplitText(String text
) {
507 GroupedComposite
.this.handleSplitText(text
);
511 textViewer
.getTextWidget().addVerifyListener(lineBreakListener
);
512 textViewer
.getTextWidget().addKeyListener(lineBreakListener
);
514 logger
.warn("Can't create line break listener because textViewer has not been initalized.");
519 * Supposed to be overridden in implementing classes
523 protected void handleSplitText(String text
) {
524 logger
.warn("No handleSplitText method defined for this composite.");
528 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
530 public void dispose () {
532 if (dragSource
!= null) {
533 dragSource
.removeDragListener(dragSourceListener
);
536 if (textViewer
!= null) {
537 StyledText textWidget
= textViewer
.getTextWidget();
539 if (focusListener
!= null) {
540 textWidget
.removeFocusListener(focusListener
);
543 if (parseListener
!= null) {
544 textWidget
.removeModifyListener(parseListener
);
547 if (lineBreakListener
!= null) {
548 textWidget
.removeVerifyListener(lineBreakListener
);
549 textWidget
.removeKeyListener(lineBreakListener
);
552 if (borderDecorator
!= null) {
553 textWidget
.removeFocusListener(borderDecorator
);
557 if (nonEditableInfoLabel
!= null && nonEditableLabelEllipsisListener
!= null) {
558 nonEditableInfoLabel
.removeControlListener(nonEditableLabelEllipsisListener
);
564 public static final String ELLIPSIS
= "...";
570 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
572 public static String
shortenText(String textValue
, Control control
) {
573 if (textValue
== null) {
576 GC gc
= new GC(control
);
577 int maxWidth
= control
.getBounds().width
;
578 int maxExtent
= gc
.textExtent(textValue
).x
;
579 if (maxExtent
< maxWidth
) {
583 int length
= textValue
.length();
584 int charsToClip
= Math
.round(0.95f
*length
* (1 - ((float)maxWidth
/maxExtent
)));
586 int end
= length
- charsToClip
;
588 String s1
= textValue
.substring(0, end
);
589 String s
= s1
+ ELLIPSIS
;
590 int l
= gc
.textExtent(s
).x
;
602 abstract class LabelEllipsisListener
extends ControlAdapter
{
607 LabelEllipsisListener(Label label
) {
611 abstract public String
getLabelText();
613 public void controlResized(ControlEvent e
) {
614 if (label
.getBounds().width
== width
) {
617 width
= label
.getBounds().width
;
618 if (label
.getBounds().width
> 0) {
620 // Dialog.shortenText(getLabelText(), label));
621 shortenText(getLabelText(), label
));
626 public void refresh(){
627 // empty default implementation
628 // make this abstract