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