0ffe2b925f0cc4535ca99b667437f4ec653be845
[taxeditor.git] / taxeditor-editor / src / main / java / eu / etaxonomy / taxeditor / editor / GroupedComposite.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;
11
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;
45
46 import eu.etaxonomy.cdm.model.taxon.Taxon;
47 import eu.etaxonomy.taxeditor.editor.name.NameViewer;
48 import eu.etaxonomy.taxeditor.store.model.Resources;
49
50 /**
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.
54 * <p>
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.
58 * </p>
59 * <p>
60 * The <code>IManagedForm</code> is also required to have a <code>Taxon</code> in its
61 * own <code>getData()</code>.
62 * </p>
63 * <p>
64 * The <code>IManagedForm</code> can also used for drawing borders by calling the method
65 * <code>createBorderSupport()</code>.
66 * </p>
67 * @author p.ciardelli
68 * @created 02.06.2008
69 * @version 1.0
70 */
71 abstract public class GroupedComposite extends Composite implements IHasPropertySource {
72 private static final Logger logger = Logger.getLogger(GroupedComposite.class);
73
74 protected AbstractTaxonEditor editor;
75
76 protected NameViewer textViewer;
77 protected IManagedForm managedForm;
78 private Label nonEditableInfoLabel;
79 private DefaultToolTip nonEditableInfoHover;
80
81 private Color defaultGroupBackgroundColor = Resources.getColor(Resources.COLOR_COMPOSITE_BACKGROUND);
82
83 protected Taxon taxon;
84
85 private CompositeBorderDecorator borderDecorator;
86 private FocusListener focusListener;
87 private LineBreakListener lineBreakListener;
88 private ParseListener parseListener;
89
90 private String emptyViewerPrompt;
91
92 public GroupedComposite(AbstractTaxonEditor editor, Composite parent){
93 super(parent, SWT.NONE);
94
95 this.editor = editor;
96 this.managedForm = editor.getManagedForm();
97
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;
102
103 createControl();
104 }
105
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;
113 setLayout(layout);
114
115 setBackground(defaultGroupBackgroundColor);
116 }
117
118 protected void createLineWrapSupport() {
119 if (textViewer instanceof NameViewer) {
120 new LineWrapSupport(textViewer, managedForm);
121 } else {
122 logger.warn("Can't create line wrap support because textViewer has not been initialized.");
123 }
124 }
125
126 public Taxon getTaxon() {
127 return taxon;
128 }
129
130 protected void createTextViewer() {
131 textViewer = new NameViewer(this);
132
133 focusListener = new FocusAdapter() {
134 public void focusGained(FocusEvent e) {
135 setFocus();
136 }
137 };
138
139 textViewer.getTextWidget().addFocusListener(focusListener);
140
141 MouseAdapter mouseListener = new MouseAdapter() {
142 public void mouseDown(MouseEvent e) {
143 setFocus();
144 }
145 };
146 this.addMouseListener(mouseListener);
147 textViewer.getRulerControl().addMouseListener(mouseListener);
148 }
149
150 /**
151 * @param icon
152 */
153 public void setIcon(Image icon) {
154 if (textViewer instanceof NameViewer) {
155 textViewer.setIcon(icon);
156 } else {
157 logger.warn("Can't set icon because textViewer has not been initialized.");
158 }
159 }
160
161 /**
162 * @param indent
163 */
164 public void setIndent(int indent) {
165 if (getLayout() instanceof TableWrapLayout) {
166 TableWrapLayout layout = ((TableWrapLayout) getLayout());
167 layout.leftMargin = indent;
168 this.setLayout(layout);
169 } else {
170 logger.warn("Couldn't indent - composite's layout must be TableWrapLayout.");
171 }
172 }
173
174 /* (non-Javadoc)
175 * @see org.eclipse.swt.widgets.Composite#setFocus()
176 */
177 public boolean setFocus() {
178 setSelection();
179 return true;
180 }
181
182 /**
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
185 */
186 public void setSelected(){
187 setBackground(Resources.getColor(Resources.COLOR_COMPOSITE_SELECTED));
188 if(textViewer != null){
189 textViewer.setCursorToEOL();
190 }
191 }
192
193 /**
194 *
195 */
196 protected void setSelection() {
197 managedForm.setInput(this);
198 }
199
200 /**
201 *
202 */
203 @Override
204 public void setBackground(Color color) {
205 super.setBackground(color);
206 if(textViewer != null){
207 textViewer.setBackground(color);
208 }
209 }
210
211 /* (non-Javadoc)
212 * @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
213 */
214 public void setFont(Font font) {
215 if (textViewer != null) {
216 textViewer.getTextWidget().setFont(font);
217 } else {
218 logger.warn("Can't set font because textViewer has not been initalized.");
219 }
220 }
221
222 public TextViewer getTextViewer() {
223 return textViewer;
224 }
225
226 /**
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"
229 * when empty.
230 *
231 * @param prompt
232 */
233 public void createEmptyViewerPrompt(final String prompt) {
234
235 emptyViewerPrompt = prompt;
236
237 Assert.isNotNull(textViewer);
238 // new EmptyTextViewerPrompt(getTextViewer(), prompt);
239
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() {
245
246
247 public void focusGained(FocusEvent e) {
248 if (document.get().equals(prompt)) {
249 textControl.setFont(getViewerFont());
250 document.set("");
251 }
252 }
253
254
255 public void focusLost(FocusEvent e) {
256 if (document.getLength() == 0) {
257 initEmptyText();
258 }
259 }
260
261 });
262 textControl.addFocusListener(getFocusListener());
263
264 if (document.getLength() == 0) {
265 textControl.setFont(promptFont);
266 document.set(prompt);
267 }
268 }
269
270 abstract protected Font getViewerFont();
271
272 protected void initEmptyText() {
273 textViewer.getTextWidget().setFont(
274 Resources
275 .getFont(Resources.FONT_DEFAULT_PROMPT));
276
277 textViewer.getDocument().set(getEmptyTextPrompt());
278 textViewer.setCursorToEOL();
279 }
280
281 protected String getEmptyTextPrompt() {
282 if (emptyViewerPrompt == null) {
283 emptyViewerPrompt = "Click to edit";
284 }
285 return emptyViewerPrompt;
286 }
287
288 private void setFocusListener(FocusListener focusListener) {
289 this.focusListener = focusListener;
290 }
291
292 private FocusListener getFocusListener() {
293 return focusListener;
294 }
295
296 /**
297 *
298 */
299 public void createBorderSupport() {
300
301 if (textViewer == null) {
302 logger.warn("Could not create border support - getTextViewer() returned null.");
303 } else {
304 borderDecorator = new CompositeBorderDecorator(
305 textViewer.getTextWidget(), managedForm);
306 borderDecorator.setLoseFocus(false);
307 textViewer.getTextWidget().addFocusListener(borderDecorator);
308 }
309 }
310
311 protected void setBorderDecorator(CompositeBorderDecorator borderDecorator) {
312 this.borderDecorator = borderDecorator;
313 }
314
315 protected CompositeBorderDecorator getBorderDecorator() {
316 return borderDecorator;
317 }
318
319 public void drawBorder() {
320 if (borderDecorator != null) {
321 borderDecorator.paintBorder();
322 }
323 }
324
325 /**
326 * @param isDirty
327 */
328 protected void setDirty(boolean isDirty) {
329 managedForm.dirtyStateChanged();
330 }
331
332 public void setMenu (Menu menu) {
333 super.setMenu(menu);
334
335 if (textViewer != null) {
336 textViewer.getRulerControl().setMenu(menu);
337 textViewer.getTextWidget().setMenu(menu);
338 }
339 }
340
341 public void unpaintBorder() {
342 if (borderDecorator != null) {
343 borderDecorator.unpaintBorder();
344 }
345 }
346
347 private Control draggableControl;
348 private DragSource dragSource;
349
350 protected void setDraggableControl(Control control) {
351 draggableControl = control;
352 }
353
354 public DragSource getDragSource() {
355 return dragSource;
356 }
357
358 public void setIsDraggable(boolean draggable) {
359
360 if (draggable) {
361
362 if (dragSource != null) {
363 // Already initialized
364 return;
365 }
366
367 if (draggableControl == null) {
368 throw new NullPointerException(
369 "Draggable control must be set to add draggability");
370 }
371
372 Transfer[] types = new Transfer[] { WidgetTransfer.getInstance() };
373 int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
374
375 dragSource = new DragSource(draggableControl, operations);
376 dragSource.setTransfer(types);
377 dragSource.addDragListener(dragSourceListener);
378
379 } else {
380 dragSource = null;
381 }
382 }
383
384 /**
385 * Drag listener which passes the Composite as the data in a drag event.
386 */
387 DragSourceListener dragSourceListener = new DragSourceAdapter() {
388
389 public void dragStart(DragSourceEvent event) {
390 if (textViewer != null) {
391 textViewer.getTextWidget().setFocus();
392 }
393 event.doit = true;
394 }
395
396 public void dragSetData(DragSourceEvent event) {
397 WidgetTransfer.getInstance().setWidget(GroupedComposite.this);
398 }
399 };
400
401 private String nonEditableText;
402 ControlListener nonEditableResizeListener = new ControlAdapter() {
403
404 int width = 0;
405
406 public void controlResized(ControlEvent e) {
407 if (nonEditableInfoLabel.getBounds().width == width) {
408 return;
409 }
410 width = nonEditableInfoLabel.getBounds().width;
411 if (nonEditableInfoLabel.getBounds().width > 0) {
412 nonEditableInfoLabel.setText(
413 Dialog.shortenText(nonEditableText.toUpperCase(),
414 nonEditableInfoLabel));
415 }
416 }
417 };
418
419 private String nonEditableHoverText;
420
421 private LabelEllipsisListener nonEditableLabelEllipsisListener;
422
423 /**
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.
427 *
428 * @param info
429 */
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;
436
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;
443 }
444 nonEditableInfoLabel.setLayoutData(layoutData);
445
446
447
448 nonEditableLabelEllipsisListener = new LabelEllipsisListener(nonEditableInfoLabel) {
449 @Override
450 public String getLabelText() {
451 return nonEditableText.toUpperCase();
452 }
453 };
454 nonEditableInfoLabel.addControlListener(nonEditableLabelEllipsisListener);
455
456 nonEditableInfoHover = new DefaultToolTip(nonEditableInfoLabel);
457 nonEditableInfoHover.setRespectDisplayBounds(true);
458
459 } else {
460 nonEditableText += ", " + info;
461 nonEditableHoverText += "\n" + info;
462
463 }
464 nonEditableInfoHover.setText(nonEditableHoverText);
465 }
466
467 /**
468 * If the user entering text requires parsing, call this method and override
469 * the method parse().
470 */
471 protected void createParser() {
472 if (textViewer != null) {
473 parseListener = new ParseListener() {
474 @Override
475 public void parse(String text) {
476 GroupedComposite.this.parse(text);
477 }
478 };
479 textViewer.getTextWidget().addModifyListener(parseListener);
480 } else {
481 logger.warn("Can't create parser because textViewer has not been initalized.");
482 }
483 }
484
485 /**
486 * Supposed to be overridden in implementing classes
487 *
488 * @param text
489 */
490 protected void parse(String text) {
491 logger.warn("No parse method defined for this composite.");
492 }
493
494 /**
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().
498 */
499 protected void createLineBreakListener() {
500 if (textViewer != null) {
501 lineBreakListener = new LineBreakListener() {
502 @Override
503 public void handleSplitText(String text) {
504 GroupedComposite.this.handleSplitText(text);
505 }
506 };
507
508 textViewer.getTextWidget().addVerifyListener(lineBreakListener);
509 textViewer.getTextWidget().addKeyListener(lineBreakListener);
510 } else {
511 logger.warn("Can't create line break listener because textViewer has not been initalized.");
512 }
513 }
514
515 /**
516 * Supposed to be overridden in implementing classes
517 *
518 * @param text
519 */
520 protected void handleSplitText(String text) {
521 logger.warn("No handleSplitText method defined for this composite.");
522 }
523
524 /* (non-Javadoc)
525 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
526 */
527 public void dispose () {
528
529 if (dragSource != null) {
530 dragSource.removeDragListener(dragSourceListener);
531 }
532
533 if (textViewer != null) {
534 StyledText textWidget = textViewer.getTextWidget();
535
536 if (focusListener != null) {
537 textWidget.removeFocusListener(focusListener);
538 }
539
540 if (parseListener != null) {
541 textWidget.removeModifyListener(parseListener);
542 }
543
544 if (lineBreakListener != null) {
545 textWidget.removeVerifyListener(lineBreakListener);
546 textWidget.removeKeyListener(lineBreakListener);
547 }
548
549 if (borderDecorator != null) {
550 textWidget.removeFocusListener(borderDecorator);
551 }
552 }
553
554 if (nonEditableInfoLabel != null && nonEditableLabelEllipsisListener != null) {
555 nonEditableInfoLabel.removeControlListener(nonEditableLabelEllipsisListener);
556 }
557
558 super.dispose();
559 }
560
561 public static final String ELLIPSIS = "...";
562
563 /**
564 * @param textValue
565 * @param control
566 * @return
567 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
568 */
569 public static String shortenText(String textValue, Control control) {
570 if (textValue == null) {
571 return null;
572 }
573 GC gc = new GC(control);
574 int maxWidth = control.getBounds().width;
575 int maxExtent = gc.textExtent(textValue).x;
576 if (maxExtent < maxWidth) {
577 gc.dispose();
578 return textValue;
579 }
580 int length = textValue.length();
581 int charsToClip = Math.round(0.95f*length * (1 - ((float)maxWidth/maxExtent)));
582
583 int end = length - charsToClip;
584 while (end > 0) {
585 String s1 = textValue.substring(0, end);
586 String s = s1 + ELLIPSIS;
587 int l = gc.textExtent(s).x;
588 if (l < maxWidth) {
589 gc.dispose();
590 return s;
591 }
592 end--;
593 }
594 gc.dispose();
595 return textValue;
596
597 }
598
599 abstract class LabelEllipsisListener extends ControlAdapter {
600
601 private Label label;
602 int width = 0;
603
604 LabelEllipsisListener(Label label) {
605 this.label = label;
606 }
607
608 abstract public String getLabelText();
609
610 public void controlResized(ControlEvent e) {
611 if (label.getBounds().width == width) {
612 return;
613 }
614 width = label.getBounds().width;
615 if (label.getBounds().width > 0) {
616 label.setText(
617 // Dialog.shortenText(getLabelText(), label));
618 shortenText(getLabelText(), label));
619 }
620 }
621 }
622 }