Project

General

Profile

Download (17.1 KB) Statistics
| Branch: | Tag: | Revision:
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
	protected ContextMenu createContextMenu() {
333
		if (textViewer != null) {
334
			ContextMenu contextMenu = new ContextMenu(textViewer.getRulerControl());
335
			textViewer.getTextWidget().setMenu(contextMenu.getMenu());
336
			return contextMenu;
337
		} else {
338
			logger.warn("Can't create menu because textViewer has not been initalized.");
339
			return null;
340
		}
341
	}
342
	
343
	public void setMenu (Menu menu) {
344
		super.setMenu(menu);
345
		
346
		if (textViewer != null) {
347
			textViewer.getRulerControl().setMenu(menu);
348
			textViewer.getTextWidget().setMenu(menu);
349
		}
350
	}
351
	
352
	public void unpaintBorder() {
353
		if (borderDecorator != null) {
354
			borderDecorator.unpaintBorder();
355
		}
356
	}
357
	
358
	private Control draggableControl;
359
	private DragSource dragSource;
360
	
361
	protected void setDraggableControl(Control control) {
362
		draggableControl = control;
363
	}
364

    
365
	public DragSource getDragSource() {
366
		return dragSource;
367
	}
368
	
369
	public void setIsDraggable(boolean draggable) {
370

    
371
		if (draggable) {
372

    
373
			if (dragSource != null) {
374
				// Already initialized
375
				return;
376
			}
377

    
378
			if (draggableControl == null) {
379
				throw new NullPointerException(
380
						"Draggable control must be set to add draggability");
381
			}
382
			
383
			Transfer[] types = new Transfer[] { WidgetTransfer.getInstance() };			
384
			int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
385

    
386
			dragSource = new DragSource(draggableControl, operations);
387
			dragSource.setTransfer(types);
388
			dragSource.addDragListener(dragSourceListener);
389

    
390
		} else {
391
			dragSource = null;
392
		}
393
	}
394

    
395
	/**
396
	 * Drag listener which passes the Composite as the data in a drag event.
397
	 */
398
	DragSourceListener dragSourceListener = new DragSourceAdapter() {
399

    
400
		public void dragStart(DragSourceEvent event) {
401
			if (textViewer != null) {
402
				textViewer.getTextWidget().setFocus();
403
			}
404
			event.doit = true;
405
		}
406
		
407
		public void dragSetData(DragSourceEvent event) {
408
			WidgetTransfer.getInstance().setWidget(GroupedComposite.this);
409
		}
410
	};
411

    
412
	private String nonEditableText;
413
	ControlListener nonEditableResizeListener = new ControlAdapter() {
414
		
415
		int width = 0;
416
		
417
		public void controlResized(ControlEvent e) {
418
			if (nonEditableInfoLabel.getBounds().width == width) {
419
				return;
420
			}
421
			width = nonEditableInfoLabel.getBounds().width;
422
			if (nonEditableInfoLabel.getBounds().width > 0) {
423
				nonEditableInfoLabel.setText(
424
						Dialog.shortenText(nonEditableText.toUpperCase(), 
425
						nonEditableInfoLabel));
426
			}
427
		}
428
	};
429

    
430
	private String nonEditableHoverText;
431

    
432
	private LabelEllipsisListener nonEditableLabelEllipsisListener;
433
			
434
	/**
435
	 * nonEditableInfo is a label displayed underneath a GroupedComposite's 
436
	 * input field. For instance, NameComposites display things like name relations,
437
	 * sec. references, etc. here.
438
	 * 
439
	 * @param info
440
	 */
441
	public void setNonEditableInfo(String info) {
442
		// TODO non editable info should only be drawn once, when everything else is drawn
443
		info = info.toUpperCase();
444
		if (nonEditableInfoLabel == null) {
445
			nonEditableText = info;
446
			nonEditableHoverText = info;
447
			
448
			nonEditableInfoLabel = new Label(this, SWT.NONE);
449
			TableWrapData layoutData = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP);
450
			// Set indent to viewer ruler's width 
451
			if (textViewer != null && textViewer.getRulerControl() != null) {
452
				// TODO right justify
453
				layoutData.indent = NameViewer.RULER_WIDTH;
454
			}
455
			nonEditableInfoLabel.setLayoutData(layoutData);
456
			
457

    
458
			
459
			nonEditableLabelEllipsisListener = new LabelEllipsisListener(nonEditableInfoLabel) {
460
				@Override
461
				public String getLabelText() {
462
					return nonEditableText.toUpperCase();
463
				}
464
			}; 
465
			nonEditableInfoLabel.addControlListener(nonEditableLabelEllipsisListener);
466
			
467
			nonEditableInfoHover = new DefaultToolTip(nonEditableInfoLabel);
468
			nonEditableInfoHover.setRespectDisplayBounds(true);
469
			
470
		} else {
471
			nonEditableText += ", " + info;
472
			nonEditableHoverText += "\n" + info;
473
			
474
		}
475
		nonEditableInfoHover.setText(nonEditableHoverText);
476
	}
477
	
478
	/**
479
	 * If the user entering text requires parsing, call this method and override
480
	 * the method parse().
481
	 */
482
	protected void createParser() {
483
		if (textViewer != null) {
484
			parseListener = new ParseListener() {
485
				@Override
486
				public void parse(String text) {
487
					GroupedComposite.this.parse(text);
488
				}
489
			};
490
			textViewer.getTextWidget().addModifyListener(parseListener);
491
		} else {
492
			logger.warn("Can't create parser because textViewer has not been initalized.");
493
		}
494
	}
495

    
496
	/**
497
	 * Supposed to be overridden in implementing classes
498
	 * 
499
	 * @param text
500
	 */
501
	protected void parse(String text) {
502
		logger.warn("No parse method defined for this composite.");
503
	}
504
	
505
	/**
506
	 * If the user hitting carriage return should cause something to happen -
507
	 * i.e. the creation of a new composite - call this method and override
508
	 * the method handleSplitText().
509
	 */
510
	protected void createLineBreakListener() {
511
		if (textViewer != null) {
512
			lineBreakListener = new LineBreakListener() {
513
				@Override
514
				public void handleSplitText(String text) {
515
					GroupedComposite.this.handleSplitText(text);	
516
				}
517
			};
518
			
519
			textViewer.getTextWidget().addVerifyListener(lineBreakListener);
520
			textViewer.getTextWidget().addKeyListener(lineBreakListener);
521
		} else {
522
			logger.warn("Can't create line break listener because textViewer has not been initalized.");
523
		}
524
	}
525
	
526
	/**
527
	 * Supposed to be overridden in implementing classes
528
	 * 
529
	 * @param text
530
	 */
531
	protected void handleSplitText(String text) {
532
		logger.warn("No handleSplitText method defined for this composite.");
533
	}
534
	
535
	/* (non-Javadoc)
536
	 * @see eu.etaxonomy.taxeditor.editor.name.GroupedComposite#dispose()
537
	 */
538
	public void dispose () {
539
		
540
		if (dragSource != null) {
541
			dragSource.removeDragListener(dragSourceListener);
542
		}		
543
		
544
		if (textViewer != null) {
545
			StyledText textWidget = textViewer.getTextWidget();
546
		
547
			if (focusListener != null) {
548
				textWidget.removeFocusListener(focusListener);
549
			}
550
			
551
			if (parseListener != null) {
552
				textWidget.removeModifyListener(parseListener);
553
			}
554
			
555
			if (lineBreakListener != null) {
556
				textWidget.removeVerifyListener(lineBreakListener);
557
				textWidget.removeKeyListener(lineBreakListener);
558
			}
559
			
560
			if (borderDecorator != null) {
561
				textWidget.removeFocusListener(borderDecorator);
562
			}
563
		}
564
		
565
		if (nonEditableInfoLabel != null && nonEditableLabelEllipsisListener != null) {
566
			nonEditableInfoLabel.removeControlListener(nonEditableLabelEllipsisListener);
567
		}
568

    
569
		super.dispose();
570
	}
571
	
572
	public static final String ELLIPSIS = "...";
573
	
574
	/**
575
	 * @param textValue
576
	 * @param control
577
	 * @return
578
	 * @see org.eclipse.jface.dialogs.Dialog#shortenText(String, Control)
579
	 */
580
	public static String shortenText(String textValue, Control control) {
581
		if (textValue == null) {
582
			return null;
583
		}
584
		GC gc = new GC(control);
585
		int maxWidth = control.getBounds().width;
586
		int maxExtent = gc.textExtent(textValue).x;
587
		if (maxExtent < maxWidth) {
588
			gc.dispose();
589
			return textValue;
590
		}
591
		int length = textValue.length();
592
		int charsToClip = Math.round(0.95f*length * (1 - ((float)maxWidth/maxExtent)));
593
		
594
		int end = length - charsToClip;
595
		while (end > 0) {
596
			String s1 = textValue.substring(0, end);
597
			String s = s1 + ELLIPSIS;
598
			int l = gc.textExtent(s).x;
599
			if (l < maxWidth) {
600
				gc.dispose();
601
				return s;
602
			}
603
			end--;
604
		}
605
		gc.dispose();
606
		return textValue;
607
		
608
	}
609
	
610
	abstract class LabelEllipsisListener extends ControlAdapter {
611
		
612
		private Label label;
613
		int width = 0;
614

    
615
		LabelEllipsisListener(Label label) {
616
			this.label = label;
617
		}
618
		
619
		abstract public String getLabelText();
620
		
621
		public void controlResized(ControlEvent e) {
622
			if (label.getBounds().width == width) {
623
				return;
624
			}
625
			width = label.getBounds().width;
626
			if (label.getBounds().width > 0) {
627
				label.setText(
628
//						Dialog.shortenText(getLabelText(), label));
629
						shortenText(getLabelText(), label));
630
			}
631
		}
632
	}
633
} 
(11-11/30)