p2izing the editor
[taxeditor.git] / eclipseprojects / eu.etaxonomy.taxeditor / src / 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.graphics.Color;
33 import org.eclipse.swt.graphics.Font;
34 import org.eclipse.swt.graphics.GC;
35 import org.eclipse.swt.graphics.Image;
36 import org.eclipse.swt.widgets.Composite;
37 import org.eclipse.swt.widgets.Control;
38 import org.eclipse.swt.widgets.Label;
39 import org.eclipse.ui.forms.IManagedForm;
40 import org.eclipse.ui.forms.widgets.TableWrapData;
41 import org.eclipse.ui.forms.widgets.TableWrapLayout;
42
43 import eu.etaxonomy.cdm.model.taxon.Taxon;
44 import eu.etaxonomy.taxeditor.ITaxEditorConstants;
45 import eu.etaxonomy.taxeditor.TaxEditorPlugin;
46 import eu.etaxonomy.taxeditor.actions.WidgetTransfer;
47 import eu.etaxonomy.taxeditor.editor.name.NameViewer;
48
49 /**
50 * Formats <code>GroupedComposite</code> with cosmetic and layout properties specific to the
51 * Editor. This should be used to maintain a consistent look and feel for all Editor
52 * freetext area components, such as DescriptionElementComposite.
53 * <p>
54 * Requires an <code>IManagedForm</code>, whose <code>input</code> is set to the contents
55 * of {@link #getData()} when the <code>GroupedComposite</code> gets focus, i.e. to
56 * populate the property sheet with the data.
57 * </p>
58 * <p>
59 * The <code>IManagedForm</code> is also required to have a <code>Taxon</code> in its
60 * own <code>getData()</code>.
61 * </p>
62 * <p>
63 * The <code>IManagedForm</code> can also used for drawing borders by calling the method
64 * <code>createBorderSupport()</code>.
65 * </p>
66 * @author p.ciardelli
67 * @created 02.06.2008
68 * @version 1.0
69 */
70 abstract public class GroupedComposite extends Composite implements IHasPropertySource {
71 private static final Logger logger = Logger.getLogger(GroupedComposite.class);
72
73 protected NameViewer textViewer;
74 protected IManagedForm managedForm;
75 private Label nonEditableInfoLabel;
76 private DefaultToolTip nonEditableInfoHover;
77
78 protected Taxon taxon;
79
80 private CompositeBorderDecorator borderDecorator;
81 private FocusListener focusListener;
82 private LineBreakListener lineBreakListener;
83 private ParseListener parseListener;
84
85 private String emptyViewerPrompt;
86
87 /**
88 * @param parent
89 * @param managedForm
90 */
91 public GroupedComposite(Composite parent, IManagedForm managedForm) {
92 super(parent, SWT.NONE);
93
94 this.managedForm = managedForm;
95
96 Object formData = managedForm.getForm().getBody().getData();
97 Assert.isTrue(formData instanceof Taxon,
98 "Managed form must have a Taxon in its data field.");
99 taxon = (Taxon) formData;
100
101 createControl();
102 }
103
104 protected void createControl() {
105 setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
106 TableWrapLayout layout = new TableWrapLayout();
107 layout.leftMargin = 0;
108 layout.topMargin = 0;
109 layout.bottomMargin = 0;
110 layout.verticalSpacing = 0;
111 setLayout(layout);
112
113 Color groupBackgroundColor = TaxEditorPlugin.getDefault().
114 getColor(ITaxEditorConstants.GROUP_GRAY_BKG_COLOR);
115 setBackground(groupBackgroundColor);
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
142 /**
143 * @param icon
144 */
145 public void setIcon(Image icon) {
146 if (textViewer instanceof NameViewer) {
147 textViewer.setIcon(icon);
148 } else {
149 logger.warn("Can't set icon because textViewer has not been initialized.");
150 }
151 }
152
153 /**
154 * @param indent
155 */
156 public void setIndent(int indent) {
157 if (getLayout() instanceof TableWrapLayout) {
158 TableWrapLayout layout = ((TableWrapLayout) getLayout());
159 layout.leftMargin = indent;
160 this.setLayout(layout);
161 } else {
162 logger.warn("Couldn't indent - composite's layout must be TableWrapLayout.");
163 }
164 }
165
166 /* (non-Javadoc)
167 * @see org.eclipse.swt.widgets.Composite#setFocus()
168 */
169 public boolean setFocus() {
170 setSelection();
171 return true;
172 }
173
174 /**
175 *
176 */
177 protected void setSelection() {
178 managedForm.setInput(this);
179 }
180
181 /* (non-Javadoc)
182 * @see org.eclipse.swt.widgets.Control#setFont(org.eclipse.swt.graphics.Font)
183 */
184 public void setFont(Font font) {
185 if (textViewer != null) {
186 textViewer.getTextWidget().setFont(font);
187 } else {
188 logger.warn("Can't set font because textViewer has not been initalized.");
189 }
190 }
191
192 public TextViewer getTextViewer() {
193 return textViewer;
194 }
195
196 /**
197 * If <code>textViewer</code> has already been set, it will show a
198 * <code>prompt</code> along the lines of "Click here to start entering data"
199 * when empty.
200 *
201 * @param prompt
202 */
203 public void createEmptyViewerPrompt(final String prompt) {
204
205 emptyViewerPrompt = prompt;
206
207 Assert.isNotNull(textViewer);
208 // new EmptyTextViewerPrompt(getTextViewer(), prompt);
209
210 final StyledText textControl = textViewer.getTextWidget();
211 final IDocument document = textViewer.getDocument();
212 final Font promptFont = TaxEditorPlugin.getDefault()
213 .getFont(ITaxEditorConstants.DEFAULT_PROMPT_FONT);
214 setFocusListener(new FocusListener() {
215
216 public void focusGained(FocusEvent e) {
217 if (document.get().equals(prompt)) {
218 textControl.setFont(getViewerFont());
219 document.set("");
220 }
221 }
222
223 public void focusLost(FocusEvent e) {
224 if (document.getLength() == 0) {
225 initEmptyText();
226 }
227 }
228 });
229 textControl.addFocusListener(getFocusListener());
230
231 if (document.getLength() == 0) {
232 textControl.setFont(promptFont);
233 document.set(prompt);
234 }
235 }
236
237
238 abstract protected Font getViewerFont();
239
240 protected void initEmptyText() {
241 textViewer.getTextWidget().setFont(
242 TaxEditorPlugin.getDefault()
243 .getFont(ITaxEditorConstants.DEFAULT_PROMPT_FONT));
244
245 textViewer.getDocument().set(getEmptyTextPrompt());
246 }
247
248 protected String getEmptyTextPrompt() {
249 if (emptyViewerPrompt == null) {
250 emptyViewerPrompt = "Click to edit";
251 }
252 return emptyViewerPrompt;
253 }
254
255 private void setFocusListener(FocusListener focusListener) {
256 this.focusListener = focusListener;
257 }
258
259 private FocusListener getFocusListener() {
260 return focusListener;
261 }
262
263 /**
264 *
265 */
266 public void createBorderSupport() {
267
268 if (textViewer == null) {
269 logger.warn("Could not create border support - getTextViewer() returned null.");
270 } else {
271 borderDecorator = new CompositeBorderDecorator(
272 textViewer.getTextWidget(), managedForm);
273 borderDecorator.setLoseFocus(false);
274 textViewer.getTextWidget().addFocusListener(borderDecorator);
275 }
276 }
277
278 protected void setBorderDecorator(CompositeBorderDecorator borderDecorator) {
279 this.borderDecorator = borderDecorator;
280 }
281
282 protected CompositeBorderDecorator getBorderDecorator() {
283 return borderDecorator;
284 }
285
286 public void drawBorder() {
287 if (borderDecorator != null) {
288 borderDecorator.paintBorder();
289 }
290 }
291
292 /**
293 * @param isDirty
294 */
295 protected void setDirty(boolean isDirty) {
296 managedForm.dirtyStateChanged();
297 }
298
299 protected ContextMenu createContextMenu() {
300 if (textViewer != null) {
301 ContextMenu contextMenu = new ContextMenu(textViewer.getRulerControl());
302 textViewer.getTextWidget().setMenu(contextMenu.getMenu());
303 return contextMenu;
304 } else {
305 logger.warn("Can't create menu because textViewer has not been initalized.");
306 return null;
307 }
308 }
309
310 public void unpaintBorder() {
311 if (borderDecorator != null) {
312 borderDecorator.unpaintBorder();
313 }
314 }
315
316 private Control draggableControl;
317 private DragSource dragSource;
318
319 protected void setDraggableControl(Control control) {
320 draggableControl = control;
321 }
322
323 public DragSource getDragSource() {
324 return dragSource;
325 }
326
327 public void setIsDraggable(boolean draggable) {
328
329 if (draggable) {
330
331 if (dragSource != null) {
332 // Already initialized
333 return;
334 }
335
336 if (draggableControl == null) {
337 throw new NullPointerException(
338 "Draggable control must be set to add draggability");
339 }
340
341 Transfer[] types = new Transfer[] { WidgetTransfer.getInstance() };
342 int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
343
344 dragSource = new DragSource(draggableControl, operations);
345 dragSource.setTransfer(types);
346 dragSource.addDragListener(dragSourceListener);
347
348 } else {
349 dragSource = null;
350 }
351 }
352
353 /**
354 * Drag listener which passes the Composite as the data in a drag event.
355 */
356 DragSourceListener dragSourceListener = new DragSourceAdapter() {
357
358 public void dragStart(DragSourceEvent event) {
359 if (textViewer != null) {
360 textViewer.getTextWidget().setFocus();
361 }
362 event.doit = true;
363 }
364
365 public void dragSetData(DragSourceEvent event) {
366 WidgetTransfer.getInstance().setWidget(GroupedComposite.this);
367 }
368 };
369
370 private String nonEditableText;
371 ControlListener nonEditableResizeListener = new ControlAdapter() {
372
373 int width = 0;
374
375 public void controlResized(ControlEvent e) {
376 if (nonEditableInfoLabel.getBounds().width == width) {
377 return;
378 }
379 width = nonEditableInfoLabel.getBounds().width;
380 if (nonEditableInfoLabel.getBounds().width > 0) {
381 nonEditableInfoLabel.setText(
382 Dialog.shortenText(nonEditableText.toUpperCase(),
383 nonEditableInfoLabel));
384 }
385 }
386 };
387
388 private String nonEditableHoverText;
389
390 private LabelEllipsisListener nonEditableLabelEllipsisListener;
391
392 /**
393 * nonEditableInfo is a label displayed underneath a GroupedComposite's
394 * input field. For instance, NameComposites display things like name relations,
395 * sec. references, etc. here.
396 *
397 * @param info
398 */
399 public void setNonEditableInfo(String info) {
400 // TODO non editable info should only be drawn once, when everything else is drawn
401 info = info.toUpperCase();
402 if (nonEditableInfoLabel == null) {
403 nonEditableText = info;
404 nonEditableHoverText = info;
405
406 nonEditableInfoLabel = new Label(this, SWT.NONE);
407 TableWrapData layoutData = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP);
408 // Set indent to viewer ruler's width
409 if (textViewer != null && textViewer.getRulerControl() != null) {
410 // TODO right justify
411 layoutData.indent = NameViewer.RULER_WIDTH;
412 }
413 nonEditableInfoLabel.setLayoutData(layoutData);
414
415
416
417 nonEditableLabelEllipsisListener = new LabelEllipsisListener(nonEditableInfoLabel) {
418 @Override
419 public String getLabelText() {
420 return nonEditableText.toUpperCase();
421 }
422 };
423 nonEditableInfoLabel.addControlListener(nonEditableLabelEllipsisListener);
424
425 nonEditableInfoHover = new DefaultToolTip(nonEditableInfoLabel);
426 nonEditableInfoHover.setRespectDisplayBounds(true);
427
428 } else {
429 nonEditableText += ", " + info;
430 nonEditableHoverText += "\n" + info;
431
432 }
433 nonEditableInfoHover.setText(nonEditableHoverText);
434 }
435
436 /**
437 * If the user entering text requires parsing, call this method and override
438 * the method parse().
439 */
440 protected void createParser() {
441 if (textViewer != null) {
442 parseListener = new ParseListener() {
443 @Override
444 public void parse(String text) {
445 GroupedComposite.this.parse(text);
446 }
447 };
448 textViewer.getTextWidget().addModifyListener(parseListener);
449 } else {
450 logger.warn("Can't create parser because textViewer has not been initalized.");
451 }
452 }
453
454 protected void parse(String text) {
455 logger.warn("No parse method defined for this composite.");
456 }
457
458 /**
459 * If the user hitting carriage return should cause something to happen -
460 * i.e. the creation of a new composite - call this method and override
461 * the method handleSplitText().
462 */
463 protected void createLineBreakListener() {
464 if (textViewer != null) {
465 lineBreakListener = new LineBreakListener() {
466 @Override
467 public void handleSplitText(String text) {
468 GroupedComposite.this.handleSplitText(text);
469 }
470 };
471
472 textViewer.getTextWidget().addVerifyListener(lineBreakListener);
473 textViewer.getTextWidget().addKeyListener(lineBreakListener);
474 } else {
475 logger.warn("Can't create line break listener because textViewer has not been initalized.");
476 }
477 }
478
479 protected void handleSplitText(String text) {
480 logger.warn("No handleSplitText method defined for this composite.");
481 }
482
483 /* (non-Javadoc)
484 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
485 */
486 public void dispose () {
487
488 if (dragSource != null) {
489 dragSource.removeDragListener(dragSourceListener);
490 }
491
492 if (textViewer != null) {
493 StyledText textWidget = textViewer.getTextWidget();
494
495 if (focusListener != null) {
496 textWidget.removeFocusListener(focusListener);
497 }
498
499 if (parseListener != null) {
500 textWidget.removeModifyListener(parseListener);
501 }
502
503 if (lineBreakListener != null) {
504 textWidget.removeVerifyListener(lineBreakListener);
505 textWidget.removeKeyListener(lineBreakListener);
506 }
507
508 if (borderDecorator != null) {
509 textWidget.removeFocusListener(borderDecorator);
510 }
511 }
512
513 if (nonEditableInfoLabel != null && nonEditableLabelEllipsisListener != null) {
514 nonEditableInfoLabel.removeControlListener(nonEditableLabelEllipsisListener);
515 }
516
517 super.dispose();
518 }
519
520 public static final String ELLIPSIS = "...";
521
522 /**
523 * @param textValue
524 * @param control
525 * @return
526 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
527 */
528 public static String shortenText(String textValue, Control control) {
529 if (textValue == null) {
530 return null;
531 }
532 GC gc = new GC(control);
533 int maxWidth = control.getBounds().width;
534 int maxExtent = gc.textExtent(textValue).x;
535 if (maxExtent < maxWidth) {
536 gc.dispose();
537 return textValue;
538 }
539 int length = textValue.length();
540 int charsToClip = Math.round(0.95f*length * (1 - ((float)maxWidth/maxExtent)));
541
542 int end = length - charsToClip;
543 while (end > 0) {
544 String s1 = textValue.substring(0, end);
545 String s = s1 + ELLIPSIS;
546 int l = gc.textExtent(s).x;
547 if (l < maxWidth) {
548 gc.dispose();
549 return s;
550 }
551 end--;
552 }
553 gc.dispose();
554 return textValue;
555
556 }
557
558 abstract class LabelEllipsisListener extends ControlAdapter {
559
560 private Label label;
561 int width = 0;
562
563 LabelEllipsisListener(Label label) {
564 this.label = label;
565 }
566
567 abstract public String getLabelText();
568
569 public void controlResized(ControlEvent e) {
570 if (label.getBounds().width == width) {
571 return;
572 }
573 width = label.getBounds().width;
574 if (label.getBounds().width > 0) {
575 label.setText(
576 // Dialog.shortenText(getLabelText(), label));
577 shortenText(getLabelText(), label));
578 }
579 }
580 }
581 }