View | Details | Raw Unified | Return to bug 162932 | Differences between
and this patch

Collapse All | Expand All

(-)src/org/eclipse/gmf/runtime/draw2d/ui/figures/WrapLabel.java (-1693 / +113 lines)
Lines 8-1714 Link Here
8
 * Contributors:
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.figures;
11
package org.eclipse.gmf.runtime.draw2d.ui.figures;
13
12
14
import java.lang.ref.WeakReference;
15
import java.util.ArrayList;
16
import java.util.Map;
17
import java.util.WeakHashMap;
18
19
import org.eclipse.draw2d.ColorConstants;
20
import org.eclipse.draw2d.Figure;
21
import org.eclipse.draw2d.FigureUtilities;
22
import org.eclipse.draw2d.Graphics;
23
import org.eclipse.draw2d.LayoutManager;
13
import org.eclipse.draw2d.LayoutManager;
24
import org.eclipse.draw2d.PositionConstants;
25
import org.eclipse.draw2d.geometry.Dimension;
26
import org.eclipse.draw2d.geometry.Insets;
27
import org.eclipse.draw2d.geometry.Point;
28
import org.eclipse.draw2d.geometry.Rectangle;
29
30
import org.eclipse.gmf.runtime.draw2d.ui.internal.mapmode.IMapModeHolder;
31
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
32
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
33
import org.eclipse.swt.graphics.Font;
34
import org.eclipse.swt.graphics.FontMetrics;
35
import org.eclipse.swt.graphics.Image;
14
import org.eclipse.swt.graphics.Image;
36
15
37
import com.ibm.icu.text.BreakIterator;
38
import com.ibm.icu.util.StringTokenizer;
39
40
16
41
/**
17
/**
42
 * An extended label that has the following extra features:
18
 * This is a legacy wrapper of the originial WrapLabel 
43
 * 
19
 * for the new LabelWithTextLayout. Since the way the layout
44
 * 1- It is capable of showing selection and focus feedback (primary or
20
 * is done in LabelWithTextLayout is different than the way it was
45
 * secondary) 2- It is capable of optionally underlining the label's text 3- It
21
 * in the original WrapLabel, this class tends to obtain the original
46
 * is capable of wrapping the label's text at a given width with a given
22
 * functionality. <p>
47
 * alignment 4- It is capable of supporting multiple label icons (temporary
48
 * feature)
49
 * 
50
 * This class was originally deriving off Draw2d's <code>Label</code> class
51
 * but with the introduction of the auto-wrapping feature, a copy had to be made
52
 * overriding was not straightforward. Hopefully, this extended version can be
53
 * pushed to opensource
54
 * 
23
 * 
55
 * <p>
24
 * @author satif
56
 * Code taken from Eclipse reference bugzilla #98820
25
 *
57
 * 
58
 * @author melaasar
59
 */
26
 */
60
public class WrapLabel
27
public class WrapLabel
61
	extends Figure
28
    extends LabelWithTextLayout {
62
	implements PositionConstants {	
29
    
63
30
    /**
64
	private static final String _ellipse = "..."; //$NON-NLS-1$
31
     * Construct a Label with no text and no icon.
65
32
     *
66
	private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0);
33
     */
67
34
    public WrapLabel() {
68
	private static final Map mapModeConstantsMap = new WeakHashMap();
35
        super();
69
36
    }
70
	private static class MapModeConstants {
37
    
71
38
    /**
72
		private static final int MAX_IMAGE_INFO = 12;
39
     * Construct a Label with passed Image as its icon.
73
40
     * 
74
		public final WeakReference mapModeRef;
41
     * @param i
75
42
     *            the label image
76
		public final int nDPtoLP_3;
43
     */
77
44
    public WrapLabel(Image i) {
78
		public final int nDPtoLP_2;
45
        super(i);
79
46
        setTextHorizontalAlignment(CENTER);
80
		public final int nDPtoLP_0;
47
    }
81
48
    
82
		public final Dimension dimension_nDPtoLP_0;
49
    /**
83
50
     * Construct a Label with passed String as the Label's text.
84
		public final WeakHashMap fontToEllipseTextSize = new WeakHashMap();
51
     * @param s the String to set as the Label's text.
85
52
     */
86
		public final SingleIconInfo[] singleIconInfos = new SingleIconInfo[MAX_IMAGE_INFO];
53
    public WrapLabel(String s) {
87
54
        super(s);
88
		public MapModeConstants(IMapMode mapMode) {
55
        setTextHorizontalAlignment(CENTER);
89
			this.mapModeRef = new WeakReference(mapMode);
56
    }
90
			nDPtoLP_2 = mapMode.DPtoLP(2);
57
    
91
			nDPtoLP_3 = mapMode.DPtoLP(3);
58
    /**
92
			nDPtoLP_0 = mapMode.DPtoLP(0);
59
     * Construct a Label with passed String as text and passed Image as its
93
			dimension_nDPtoLP_0 = new Dimension(nDPtoLP_0, nDPtoLP_0);
60
     * icon.
94
		}
61
     * 
95
62
     * @param s
96
		public Dimension getEllipseTextSize(Font f) {
63
     *            the label text
97
			Dimension d = (Dimension) fontToEllipseTextSize.get(f);
64
     * @param i
98
			if (d == null) {
65
     *            the label image
99
				IMapMode mapMode = (IMapMode) mapModeRef.get();
66
     */
100
				d = FigureUtilities.getTextExtents(_ellipse, f);
67
    public WrapLabel(String s, Image i) {
101
				d.height = FigureUtilities.getFontMetrics(f).getHeight();
68
        super(s,i);
102
				d = new Dimension(mapMode.DPtoLP(d.width), mapMode
69
        super.setTextHorizontalAlignment(CENTER);
103
					.DPtoLP(d.height));
70
    }
104
				fontToEllipseTextSize.put(f, d);
71
    
105
			}
72
    /* (non-Javadoc)
106
			return d;
73
     * @see org.eclipse.gmf.runtime.draw2d.ui.figures.LabelWithTextLayout#setLabelAlignment(int)
107
		}
74
     */
108
75
    public void setLabelAlignment(int align) {
109
		public SingleIconInfo getSingleIconInfo(Image image) {
76
        if ((align & (LEFT_CENTER_RIGHT)) != 0) {
110
			if (image == null) {
77
            super.setLabelAlignment(CENTER);
111
				return SingleIconInfo.NULL_INFO;
78
            setTextHorizontalAlignment(align);
112
			}
79
        }
113
			SingleIconInfo info;
80
        else
114
			for (int i = 0; i < MAX_IMAGE_INFO; ++i) {
81
            super.setLabelAlignment(align);
115
				info = singleIconInfos[i];
82
    }
116
				if (info == null) {
83
    
117
					info = new SingleIconInfo(image);
84
    /**
118
					singleIconInfos[i] = info;
85
     * @param i
119
					return info;
86
     *            The label text horizontal alignment
120
				}
87
     */
121
				if (info.icon == image) {
88
    public void setTextWrapAlignment(int i) {
122
					return info;
89
        if ((getLabelAlignment() & (TOP | BOTTOM)) != 0) {
123
				}
90
            if ((i & (RIGHT | CENTER)) != 0)
124
			}
91
                setTextHorizontalAlignment(i);
125
			int index = SingleIconInfo.count % MAX_IMAGE_INFO;
92
            else
126
			info = new SingleIconInfo(image);
93
                setTextHorizontalAlignment(LEFT);
127
			singleIconInfos[index] = info;
94
        }
128
			return info;
95
        else if ((i == CENTER) && 
129
		}
96
                 (getLabelAlignment() == CENTER) &&
130
	}
97
                 (super.getTextHorizontalAlignment() != CENTER)) {
131
98
            setTextHorizontalAlignment(i);
132
	// reserve 1 bit
99
        }
133
	private static int FLAG_SELECTED = MAX_FLAG << 1;
100
    }
134
101
    
135
	private static int FLAG_HASFOCUS = MAX_FLAG << 2;
102
    /**
136
103
     * The horizontal orientation of the text.
137
	private static int FLAG_UNDERLINED = MAX_FLAG << 3;
104
     * 
138
105
     * @see WrapLabel#getTextHorizontalAlignment()
139
	private static int FLAG_STRIKEDTHROUGH = MAX_FLAG << 4;
106
     */
140
107
    public int getTextWrapAlignment() {
141
	private static int FLAG_WRAP = MAX_FLAG << 5;
108
        return getTextHorizontalAlignment();
142
109
    }
143
	// reserve 3 bits
110
    
144
	private static int FLAG_TEXT_ALIGN = MAX_FLAG << 6;
111
    /**
145
112
     * We do not want to allow anyone else to control the layout of the
146
	private static int FLAG_WRAP_ALIGN = MAX_FLAG << 9;
113
     * WrapLabel, which is why the manager parameter can only be
147
114
     * of the type OffsetLayout that is defined within WrapLabel 
148
	private static int FLAG_ICON_ALIGN = MAX_FLAG << 12;
115
     *  
149
116
     * @see org.eclipse.draw2d.Figure#setLayoutManager(org.eclipse.draw2d.LayoutManager)
150
	private static int FLAG_LABEL_ALIGN = MAX_FLAG << 15;
117
     */
151
118
    public void setLayoutManager(LayoutManager manager) {
152
	private static int FLAG_TEXT_PLACEMENT = MAX_FLAG << 18;
119
        // we want to use the OffsetLayout only within WrapLabel.
153
120
        // This will prevent any client from trying to change
154
	private MapModeConstants mapModeConstants;
121
        // the layout of the contents within WrapLabel.
155
122
        if (manager instanceof OffsetLayout)
156
	/** the original label's text */
123
            super.setLayoutManager(manager);
157
	private String text;
124
    }
158
159
	/** the label's text used in painting after applying required styles */
160
	private String subStringText;
161
162
	/** the size of text */
163
	private Dimension textSize;	
164
165
	private Dimension ellipseTextSize;
166
167
	/** the location of text */
168
	private Point textLocation;
169
170
	/** the cached hint used to calculate text size */
171
	private int cachedPrefSizeHint_width;
172
173
	private int cachedPrefSizeHint_height;
174
175
	/** the icon location */
176
	private Point iconLocation;
177
178
	private static abstract class IconInfo {
179
		/**
180
		 * Gets the icon at the index location.
181
		 * 
182
		 * @param i
183
		 *            the index to retrieve the icon of
184
		 * @return <code>Image</code> that corresponds to the given index.
185
		 */
186
		public abstract Image getIcon(int i);
187
		
188
		/**
189
		 * Gets the icon size of the icon at the given index.
190
		 * 
191
		 * @param i
192
		 * @return the <code>Dimension</code> that is the size of the icon at
193
		 *         the given index.
194
		 */
195
		public abstract Dimension getIconSize(IMapMode mapMode, int i);
196
197
		/**
198
		 * @return the number of icons
199
		 */
200
		public abstract int getNumberofIcons();
201
		
202
		/**
203
		 * @return the <code>Dimension</code> that is the total size of all
204
		 *         the icons.
205
		 */
206
		public abstract Dimension getTotalIconSize(IMapMode mapMode);
207
208
		public abstract void invalidate();
209
		
210
		/**
211
		 * Sets the icon at the index location.
212
		 * 
213
		 * @param icon
214
		 * @param i
215
		 */
216
		public abstract void setIcon(Image icon, int i);
217
		
218
		/**
219
		 * 
220
		 */
221
		public abstract int getMaxIcons();
222
223
	}	
224
225
	private static class SingleIconInfo
226
		extends IconInfo {	
227
228
		static int count;
229
		
230
		public static final SingleIconInfo NULL_INFO = new SingleIconInfo(){
231
			public int getNumberofIcons() {
232
				return 0;
233
			}
234
		};
235
236
		final Image icon;
237
238
		/** total icon size */
239
		private Dimension totalIconSize;
240
241
		private SingleIconInfo() {
242
			icon = null;//don't increment count, used only for NULL_INFO
243
		}
244
245
		public SingleIconInfo(Image icon) {
246
			this.icon = icon;
247
			++count;
248
		}
249
250
		public final int getMaxIcons() {
251
			return 1;
252
		}
253
254
		
255
		public Image getIcon(int i) {
256
			if (i == 0) {
257
				return icon;
258
			} else if (i > 0) {
259
				return null;
260
			}
261
			throw new IndexOutOfBoundsException();
262
		}
263
264
		
265
		public void setIcon(Image img, int i) {
266
			throw new UnsupportedOperationException();
267
		}
268
269
		
270
		public Dimension getIconSize(IMapMode mapMode, int i) {
271
			if (i == 0) {
272
				return getTotalIconSize(mapMode);
273
			}
274
275
			throw new IndexOutOfBoundsException();
276
		}
277
278
		
279
		public int getNumberofIcons() {
280
			return 1;
281
		}
282
283
		
284
		public Dimension getTotalIconSize(IMapMode mapMode) {
285
			if (totalIconSize != null)
286
				return totalIconSize;
287
288
			if (icon != null && !icon.isDisposed()) {
289
				org.eclipse.swt.graphics.Rectangle imgBounds = icon.getBounds();
290
				totalIconSize = new Dimension(mapMode.DPtoLP(imgBounds.width),
291
					mapMode.DPtoLP(imgBounds.height));
292
			} else {
293
				totalIconSize = EMPTY_DIMENSION;
294
			}
295
296
			return totalIconSize;
297
		}
298
299
		
300
		public void invalidate() {
301
			totalIconSize = null;
302
		}
303
304
	}
305
306
	private static class MultiIconInfo
307
		extends IconInfo {
308
309
		/** the label icons */
310
		private ArrayList icons = new ArrayList(2);
311
312
		/** total icon size */
313
		private Dimension totalIconSize;
314
315
		public MultiIconInfo() {
316
			super();
317
		}
318
319
		public int getMaxIcons() {
320
			return -1;
321
		}
322
323
		/**
324
		 * Gets the icon at the index location.
325
		 * 
326
		 * @param i
327
		 *            the index to retrieve the icon of
328
		 * @return <code>Image</code> that corresponds to the given index.
329
		 */
330
		public Image getIcon(int i) {
331
			if (i >= icons.size())
332
				return null;
333
334
			return (Image) icons.get(i);
335
		}
336
337
		/**
338
		 * Sets the icon at the index location.
339
		 * 
340
		 * @param icon
341
		 * @param i
342
		 */
343
		public void setIcon(Image icon, int i) {
344
			int size = icons.size();
345
			if (i >= size) {
346
				for (int j = size; j < i; j++)
347
					icons.add(null);
348
				icons.add(icon);
349
				icons.trimToSize();
350
			} else
351
				icons.set(i, icon);
352
		}
353
354
		/**
355
		 * Gets the icon size of the icon at the given index.
356
		 * 
357
		 * @param i
358
		 * @return the <code>Dimension</code> that is the size of the icon at
359
		 *         the given index.
360
		 */
361
		public Dimension getIconSize(IMapMode mapMode, int i) {
362
			Image img = getIcon(i);
363
			if (img != null && !img.isDisposed()) {
364
				org.eclipse.swt.graphics.Rectangle imgBounds = img.getBounds();				
365
				return new Dimension(mapMode.DPtoLP(imgBounds.width), mapMode
366
					.DPtoLP(imgBounds.height));
367
			}
368
			return EMPTY_DIMENSION;
369
		}
370
371
		/**
372
		 * @return the number of icons
373
		 */
374
		public int getNumberofIcons() {
375
			return icons.size();
376
		}
377
378
		/**
379
		 * @return the <code>Dimension</code> that is the total size of all
380
		 *         the icons.
381
		 */
382
		public Dimension getTotalIconSize(IMapMode mapMode) {
383
			if (totalIconSize != null)
384
				return totalIconSize;
385
			int iconNum = getNumberofIcons();
386
			if (iconNum == 0) {
387
				return totalIconSize = EMPTY_DIMENSION;
388
			}
389
390
			totalIconSize = new Dimension();
391
			for (int i = 0; i < iconNum; i++) {
392
				Dimension iconSize = getIconSize(mapMode, i);
393
				totalIconSize.width += iconSize.width;
394
				if (iconSize.height > totalIconSize.height)
395
					totalIconSize.height = iconSize.height;
396
			}
397
398
			return totalIconSize;
399
		}
400
401
		/**
402
		 * 
403
		 */
404
		public void invalidate() {
405
			totalIconSize = null;
406
		}
407
	}
408
409
	private IconInfo iconInfo;
410
411
	/** the cached hint used to calculate text size */	
412
	private int cachedTextSizeHint_width;
413
414
	private int cachedTextSizeHint_height;
415
	
416
	
417
	
418
	/**
419
	 * Construct an empty Label.
420
	 * 
421
	 * @since 2.0
422
	 */
423
	public WrapLabel() {
424
		text = "";//$NON-NLS-1$
425
		// set defaults
426
		setAlignmentFlags(CENTER, FLAG_TEXT_ALIGN);
427
		setAlignmentFlags(CENTER, FLAG_ICON_ALIGN);
428
		setAlignmentFlags(CENTER, FLAG_LABEL_ALIGN);
429
		setAlignmentFlags(LEFT, FLAG_WRAP_ALIGN);
430
		setPlacementFlags(EAST, FLAG_TEXT_PLACEMENT);
431
	}
432
433
	/**
434
	 * Construct a Label with passed String as its text.
435
	 * 
436
	 * @param s the label text
437
	 * @since 2.0
438
	 */
439
	public WrapLabel(String s) {
440
		if (s != null) {
441
			text = s;
442
		} else {
443
			text = "";//$NON-NLS-1$
444
		}
445
//		setBorder(new LineBorderEx(ColorConstants.red,3));
446
	}
447
448
	/**
449
	 * Construct a Label with passed Image as its icon.
450
	 * 
451
	 * @param i the label image
452
	 * @since 2.0
453
	 */
454
	public WrapLabel(Image i) {
455
		text = "";//$NON-NLS-1$
456
		iconInfo = new SingleIconInfo(i);
457
	}
458
459
	/**
460
	 * Construct a Label with passed String as text and passed Image as its
461
	 * icon.
462
	 * 
463
	 * @param s the label text
464
	 * @param i the label image
465
	 * @since 2.0
466
	 */
467
	public WrapLabel(String s, Image i) {
468
		if (s != null) {
469
			text = s;
470
		} else {
471
			text = "";//$NON-NLS-1$
472
		}
473
		iconInfo = new SingleIconInfo(i);
474
	}
475
	
476
	/**
477
	 * @return <code>IMapMode</code> used by this figure.
478
	 *         <code>IMapMode</code> that allows for the coordinate mapping
479
	 *         from device to logical units.
480
	 */
481
	private IMapMode getMapMode() {
482
		return (IMapMode) getMapModeConstants().mapModeRef.get();
483
	}
484
485
	private MapModeConstants getMapModeConstants() {
486
		if (mapModeConstants == null) {
487
			IMapMode mapMode = MapModeUtil.getMapMode(this);
488
			while (mapMode instanceof IMapModeHolder) {
489
				mapMode = ((IMapModeHolder) mapMode).getMapMode();
490
			}
491
			mapModeConstants = (MapModeConstants) mapModeConstantsMap
492
				.get(mapMode);
493
			if (mapModeConstants == null) {
494
				mapModeConstants = new MapModeConstants(mapMode);
495
				mapModeConstantsMap.put(mapMode, mapModeConstants);
496
			}
497
		}
498
		return mapModeConstants;
499
	}
500
501
	private void alignOnHeight(Point loc, Dimension size, int alignment) {
502
		switch (alignment) {
503
			case TOP:
504
				loc.y = getInsets().top;
505
				break;
506
			case BOTTOM:
507
				loc.y = bounds.height - size.height - getInsets().bottom;
508
				break;
509
			default:
510
				loc.y = (bounds.height - size.height) / 2;
511
		}
512
	}
513
514
	private void alignOnWidth(Point loc, Dimension size, int alignment) {
515
		switch (alignment) {
516
			case LEFT:
517
				loc.x = getInsets().left;
518
				break;
519
			case RIGHT:
520
				loc.x = bounds.width - size.width - getInsets().right;
521
				break;
522
			default:
523
				loc.x = (bounds.width - size.width) / 2;
524
		}
525
	}
526
527
	private void calculateAlignment(Dimension iconSize, int textPlacement) {
528
		switch (textPlacement) {
529
			case EAST:
530
			case WEST:
531
				alignOnHeight(textLocation, getTextSize(), getTextAlignment());
532
				alignOnHeight(getIconLocation(), iconSize, getIconAlignment());
533
				break;
534
			case NORTH:
535
			case SOUTH:
536
				alignOnWidth(textLocation, getSubStringTextSize(),
537
					getTextAlignment());
538
				alignOnWidth(getIconLocation(), iconSize, getIconAlignment());
539
				break;
540
		}
541
	}
542
543
	/**
544
	 * Calculates the size of the Label using the passed Dimension as the size
545
	 * of the Label's text.
546
	 * 
547
	 * @param txtSize the precalculated size of the label's text
548
	 * @return the label's size
549
	 * @since 2.0
550
	 */
551
	protected Dimension calculateLabelSize(Dimension txtSize) {
552
		Dimension iconSize = getTotalIconSize();
553
		boolean isEmpty = (iconSize.width == 0 && iconSize.height == 0);
554
		int len = getText().length();
555
		if (len == 0 && isEmpty) {
556
			return new Dimension(txtSize.width, txtSize.height);
557
		}
558
		int gap = (len == 0 || isEmpty) ? 0
559
			: getIconTextGap();
560
		int placement = getTextPlacement();
561
		if (placement == WEST || placement == EAST) {
562
			return new Dimension(iconSize.width + gap + txtSize.width, Math
563
				.max(iconSize.height, txtSize.height));
564
		} else {
565
			return new Dimension(Math.max(iconSize.width, txtSize.width),
566
				iconSize.height + gap + txtSize.height);
567
		}
568
	}
569
570
	private void calculateLocations() {
571
		textLocation = new Point();
572
		iconLocation = new Point();
573
		Dimension iconSize = getTotalIconSize();
574
		int textPlacement = getTextPlacement();
575
		calculatePlacement(iconSize, textPlacement);
576
		calculateAlignment(iconSize, textPlacement);
577
		Rectangle r = getBounds();
578
		Dimension ps = getPreferredSize(r.width, r.height);
579
		int w = (r.width - ps.width)
580
			+ (getTextSize().width - getSubStringTextSize().width);
581
		int h = r.height - ps.height;
582
		if (w == 0 && h == 0) {
583
			return;
584
		}
585
586
		Dimension offset = new Dimension(w, h);
587
		switch (getLabelAlignment()) {
588
			case CENTER:
589
				offset.scale(0.5f);
590
				break;
591
			case LEFT:
592
				offset.scale(0.0f);
593
				break;
594
			case RIGHT:
595
				offset.scale(1.0f);
596
				break;
597
			case TOP:
598
				offset.height = 0;
599
				offset.scale(0.5f);
600
				break;
601
			case BOTTOM:
602
				offset.height = offset.height * 2;
603
				offset.scale(0.5f);
604
				break;
605
			default:
606
				offset.scale(0.5f);
607
				break;
608
		}
609
610
		switch (textPlacement) {
611
			case EAST:
612
			case WEST:
613
				offset.height = 0;
614
				break;
615
			case NORTH:
616
			case SOUTH:
617
				offset.width = 0;
618
				break;
619
		}
620
621
		textLocation.translate(offset);
622
		iconLocation.translate(offset);
623
	}
624
625
	private void calculatePlacement(Dimension iconSize, int textPlacement) {
626
		int gap = (getText().length() == 0 || (iconSize.width == 0 && iconSize.height == 0)) ? 0
627
			: getIconTextGap();
628
		Insets insets = getInsets();
629
		switch (textPlacement) {
630
			case EAST:
631
				iconLocation.x = insets.left;
632
				textLocation.x = iconSize.width + gap + insets.left;
633
				break;
634
			case WEST:
635
				textLocation.x = insets.left;
636
				iconLocation.x = getSubStringTextSize().width + gap
637
					+ insets.left;
638
				break;
639
			case NORTH:
640
				textLocation.y = insets.top;
641
				iconLocation.y = getTextSize().height + gap + insets.top;
642
				break;
643
			case SOUTH:
644
				textLocation.y = iconSize.height + gap + insets.top;
645
				iconLocation.y = insets.top;
646
		}
647
	}
648
	/**
649
	 * Calculates the size of the Label's text size. The text size calculated
650
	 * takes into consideration if the Label's text is currently truncated. If
651
	 * text size without considering current truncation is desired, use
652
	 * {@link #calculateTextSize(int, int)}.
653
	 * 
654
	 * @return the size of the label's text, taking into account truncation
655
	 * @since 2.0
656
	 */
657
	protected Dimension calculateSubStringTextSize() {
658
		Font f = getFont();
659
		return getTextExtents(getSubStringText(), f, getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight())); 
660
	}
661
662
	/**
663
	 * Calculates and returns the size of the Label's text. Note that this
664
	 * Dimension is calculated using the Label's full text, regardless of
665
	 * whether or not its text is currently truncated. If text size considering
666
	 * current truncation is desired, use {@link #calculateSubStringTextSize()}.
667
	 * 
668
	 * @param wHint a width hint
669
	 * @param hHint a height hint
670
	 * @return the size of the label's text, ignoring truncation
671
	 * @since 2.0
672
	 */
673
	protected Dimension calculateTextSize(int wHint, int hHint) {
674
		Font f = getFont();
675
		return getTextExtents(getWrappedText(wHint, hHint), f,getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight()));
676
	}
677
678
	private void clearLocations() {
679
		iconLocation = textLocation = null;
680
	}
681
682
	/**
683
	 * Returns the Label's icon.
684
	 * 
685
	 * @return the label icon
686
	 * @since 2.0
687
	 */
688
	public Image getIcon() {
689
		return getIcon(0);
690
	}
691
692
	/**
693
	 * Gets the label's icon at the given index
694
	 * 
695
	 * @param index The icon index
696
	 * @return the <code>Image</code> that is the icon for the given index.
697
	 */
698
	public Image getIcon(int index) {
699
		if (iconInfo == null)
700
			return null;
701
		return iconInfo.getIcon(index);
702
	}
703
704
	/**
705
	 * Determines if there is any icons by checking if icon size is zeros.
706
	 * 
707
	 * @return true if icons are present, false otherwise 
708
	 */
709
	protected boolean hasIcons() {
710
		return (getNumberofIcons() > 0);
711
	}
712
713
	/**
714
	 * Returns the current alignment of the Label's icon. The default is
715
	 * {@link PositionConstants#CENTER}.
716
	 * 
717
	 * @return the icon alignment
718
	 * @since 2.0
719
	 */
720
	public int getIconAlignment() {
721
		return getAlignment(FLAG_ICON_ALIGN);
722
	}
723
724
	/**
725
	 * Returns the bounds of the Label's icon.
726
	 * 
727
	 * @return the icon's bounds
728
	 * @since 2.0
729
	 */
730
	public Rectangle getIconBounds() {
731
		return new Rectangle(getBounds().getLocation().translate(
732
			getIconLocation()), getTotalIconSize());
733
	}
734
735
	/**
736
	 * Returns the location of the Label's icon relative to the Label.
737
	 * 
738
	 * @return the icon's location
739
	 * @since 2.0
740
	 */
741
	protected Point getIconLocation() {
742
		if (iconLocation == null)
743
			calculateLocations();
744
		return iconLocation;
745
	}
746
747
	/**
748
	 * Returns the gap in pixels between the Label's icon and its text.
749
	 * 
750
	 * @return the gap
751
	 * @since 2.0
752
	 */
753
	public int getIconTextGap() {
754
		return getMapModeConstants().nDPtoLP_3;
755
	}
756
757
	/**
758
	 * @see IFigure#getMinimumSize(int, int)
759
	 */
760
	public Dimension getMinimumSize(int w, int h) {
761
		if (minSize != null)
762
			return minSize;
763
		minSize = new Dimension();
764
		LayoutManager layoutManager = getLayoutManager();
765
		if (layoutManager != null)
766
			minSize.setSize(layoutManager.getMinimumSize(this, w, h));
767
		Font f = getFont();
768
		Dimension d = getEllipseTextSize().getIntersected(
769
			getTextExtents(getText(), f, getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight())));		
770
		
771
		Dimension labelSize = calculateLabelSize(d);
772
		Insets insets = getInsets();
773
		labelSize.expand(insets.getWidth(), insets.getHeight());
774
		minSize.union(labelSize);
775
		return minSize;
776
	}
777
778
	/* 
779
	 * (non-Javadoc)
780
	 * @see org.eclipse.draw2d.IFigure#getPreferredSize(int, int)
781
	 */
782
	public Dimension getPreferredSize(int wHint, int hHint) {
783
		if (prefSize == null || wHint != cachedPrefSizeHint_width || hHint != cachedPrefSizeHint_height) {
784
			prefSize = calculateLabelSize(getTextSize(wHint, hHint));
785
			Insets insets = getInsets();
786
			prefSize.expand(insets.getWidth(), insets.getHeight());
787
			LayoutManager layoutManager = getLayoutManager();
788
			if (layoutManager != null) {
789
				prefSize.union(layoutManager.getPreferredSize(this, wHint,
790
					hHint));
791
			}
792
			prefSize.union(getMinimumSize(wHint, hHint));
793
			cachedPrefSizeHint_width = wHint;
794
			cachedPrefSizeHint_height = hHint;
795
		}
796
		return prefSize;
797
	}
798
799
	/* (non-Javadoc)
800
	 * @see org.eclipse.draw2d.IFigure#getMaximumSize()
801
	 */
802
	public Dimension getMaximumSize() {
803
		// this assumes that getPreferredSize(wHint, hHint) is called before
804
		return prefSize;   
805
	}
806
807
	/**
808
	 * Calculates the amount of the Label's current text will fit in the Label,
809
	 * including an elipsis "..." if truncation is required.
810
	 * 
811
	 * @return the substring
812
	 * @since 2.0
813
	 */
814
	public String getSubStringText() {
815
		if (subStringText != null)
816
			return subStringText;
817
		
818
		String theText = getText();
819
		int textLen = theText.length();
820
		if (textLen == 0) {
821
			return subStringText = "";//$NON-NLS-1$;;
822
		}
823
		Dimension size = getSize();
824
		Dimension shrink = getPreferredSize(size.width, size.height).getDifference(size);
825
		Dimension effectiveSize = getTextSize().getExpanded(-shrink.width, -shrink.height);
826
		
827
		if (effectiveSize.height == 0) {
828
			return subStringText = "";//$NON-NLS-1$;
829
		}
830
		
831
		Font f = getFont();
832
		FontMetrics metrics = FigureUtilities.getFontMetrics(f);
833
		IMapMode mm = getMapMode();
834
		int fontHeight = mm.DPtoLP(metrics.getHeight());
835
		int charAverageWidth = mm.DPtoLP(metrics.getAverageCharWidth());
836
		int maxLines = (int) (effectiveSize.height / (double) fontHeight);
837
		if (maxLines == 0) {
838
			return subStringText = "";//$NON-NLS-1$
839
		}
840
841
		StringBuffer accumlatedText = new StringBuffer();
842
		StringBuffer remainingText = new StringBuffer(theText);
843
		
844
		int effectiveSizeWidth = effectiveSize.width;
845
		int widthHint = Math.max(effectiveSizeWidth
846
			- getEllipseTextSize().width, 0);
847
		int i = 0, j = 0;
848
		while (remainingText.length() > 0 && j++ < maxLines) {
849
			i = getLineWrapPosition(remainingText.toString(), f, effectiveSizeWidth, fontHeight);
850
851
			if (accumlatedText.length() > 0)
852
				accumlatedText.append('\n');
853
854
			if (i == 0 || (remainingText.length() > i && j == maxLines)) {				
855
				i = getLargestSubstringConfinedTo(remainingText.toString(), f, widthHint, fontHeight, charAverageWidth);
856
				accumlatedText.append(remainingText.substring(0, i));
857
				accumlatedText.append(getEllipse());
858
			} else
859
				accumlatedText.append(remainingText.substring(0, i));
860
			remainingText.delete(0, i);
861
		}
862
		return subStringText = accumlatedText.toString();
863
	}
864
	
865
	
866
	
867
	
868
	/**
869
	 * Creates an equivalent text to that of the label's but with "\n"(s)
870
	 * inserted at the wrapping positions. This method assumes unlimited
871
	 * bounding box and is used by <code>calculateTextSize()</code> to
872
	 * calculate the perfect size of the text with wrapping
873
	 * 
874
	 * @return the wrapped text
875
	 */	
876
	private String getWrappedText(int wHint, int hHint) {
877
		String theText = getText();		
878
		if (wHint == -1 || theText.length() == 0 || !isTextWrapped())
879
			return theText;
880
881
		Dimension iconSize = getTotalIconSize();
882
		if (!(iconSize.width == 0 && iconSize.height == 0)) {
883
			switch(getTextPlacement()) {
884
				case EAST:
885
				case WEST:
886
					wHint -= iconSize.width + getIconTextGap();
887
					break;
888
				case NORTH:
889
				case SOUTH:
890
					if (hHint != -1)
891
						hHint -= iconSize.height + getIconTextGap();
892
					break;
893
			}
894
		}
895
		
896
		
897
		if ((hHint == 0)||(wHint == 0)) {
898
			return "";//$NON-NLS-1$;
899
		}
900
		
901
		Font f = getFont();
902
		int fontHeight = getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight());
903
		int maxLines = Integer.MAX_VALUE;
904
		if (hHint != -1) {			
905
			maxLines = (int) (hHint / (double) fontHeight);
906
			if (maxLines == 0) {
907
				return "";//$NON-NLS-1$;;
908
			}
909
		}	
910
		
911
		StringBuffer accumlatedText = new StringBuffer();
912
		StringBuffer remainingText = new StringBuffer(theText);
913
		int i = 0, j = 0;
914
915
		while (remainingText.length() > 0 && j++  < maxLines) {
916
			if ((i = getLineWrapPosition(remainingText.toString(), f, wHint, fontHeight)) == 0)
917
				break;
918
919
			if (accumlatedText.length() > 0)
920
				accumlatedText.append('\n');
921
			accumlatedText.append(remainingText.substring(0, i));
922
			remainingText.delete(0, i);
923
		}
924
		return accumlatedText.toString();
925
	}
926
927
	/**
928
	 * Returns the size of the Label's current text. If the text is currently
929
	 * truncated, the truncated text with its ellipsis is used to calculate the
930
	 * size.
931
	 * 
932
	 * @return the size of this label's text, taking into account truncation
933
	 * @since 2.0
934
	 */
935
	protected Dimension getSubStringTextSize() {
936
		return calculateSubStringTextSize();
937
	}
938
	
939
	/**
940
	 * Returns the size of the String constant "..." the ellipse based on
941
	 * the currently used Map mode
942
	 * size.
943
	 * 
944
	 * @return the size of ellipse text
945
	 * 
946
	 */
947
	private Dimension getEllipseTextSize() {
948
		if (ellipseTextSize == null) {
949
			ellipseTextSize = getMapModeConstants().getEllipseTextSize(
950
				getFont());
951
		}
952
		return ellipseTextSize;
953
	}
954
955
	/**
956
	 * Returns the text of the label. Note that this is the complete text of the
957
	 * label, regardless of whether it is currently being truncated. Call
958
	 * {@link #getSubStringText()}to return the label's current text contents
959
	 * with truncation considered.
960
	 * 
961
	 * @return the complete text of this label
962
	 * @since 2.0
963
	 */
964
	public String getText() {
965
		return text;
966
	}
967
968
	/**
969
	 * Returns the current alignment of the Label's text. The default text
970
	 * alignment is {@link PositionConstants#CENTER}.
971
	 * 
972
	 * @return the text alignment
973
	 */
974
	public int getTextAlignment() {
975
		return getAlignment(FLAG_TEXT_ALIGN);
976
	}
977
978
	/**
979
	 * Returns the current alignment of the entire Label. The default label
980
	 * alignment is {@link PositionConstants#LEFT}.
981
	 * 
982
	 * @return the label alignment
983
	 */
984
	private int getLabelAlignment() {
985
		return getAlignment(FLAG_LABEL_ALIGN);
986
	}
987
	
988
	/**
989
	 * Returns the bounds of the label's text. Note that the bounds are
990
	 * calculated using the label's complete text regardless of whether the
991
	 * label's text is currently truncated.
992
	 * 
993
	 * @return the bounds of this label's complete text
994
	 * @since 2.0
995
	 */
996
	public Rectangle getTextBounds() {
997
		return new Rectangle(getBounds().getLocation().translate(
998
			getTextLocation()), getTextSize());
999
	}
1000
1001
	/**
1002
	 * Returns the location of the label's text relative to the label.
1003
	 * 
1004
	 * @return the text location
1005
	 * @since 2.0
1006
	 */
1007
	protected Point getTextLocation() {
1008
		if (textLocation != null)
1009
			return textLocation;
1010
		calculateLocations();
1011
		return textLocation;
1012
	}
1013
1014
	/**
1015
	 * Returns the current placement of the label's text relative to its icon.
1016
	 * The default text placement is {@link PositionConstants#EAST}.
1017
	 * 
1018
	 * @return the text placement
1019
	 * @since 2.0
1020
	 */
1021
	public int getTextPlacement() {
1022
		return getPlacement(FLAG_TEXT_PLACEMENT);
1023
	}
1024
1025
	/**
1026
	 * Returns the size of the label's complete text. Note that the text used to
1027
	 * make this calculation is the label's full text, regardless of whether the
1028
	 * label's text is currently being truncated and is displaying an ellipsis.
1029
	 * If the size considering current truncation is desired, call
1030
	 * {@link #getSubStringTextSize()}.
1031
	 * 
1032
	 * @param wHint a width hint
1033
	 * @param hHint a height hint
1034
	 * @return the size of this label's complete text
1035
	 * @since 2.0
1036
	 */
1037
	protected Dimension getTextSize(int wHint, int hHint) {
1038
		if (textSize == null || wHint != cachedTextSizeHint_width || hHint != cachedTextSizeHint_height) {
1039
			textSize = calculateTextSize(wHint, hHint);
1040
			cachedTextSizeHint_width = wHint;
1041
			cachedTextSizeHint_height= hHint;
1042
		}
1043
		return textSize;
1044
	}
1045
1046
	/**
1047
	 * Gets the text size given the current size as a width hint
1048
	 */
1049
	private final Dimension getTextSize() {		
1050
		Rectangle r = getBounds();
1051
		return getTextSize(r.width, r.height);		
1052
	}
1053
	
1054
	/**
1055
	 * @see IFigure#invalidate()
1056
	 */
1057
	public void invalidate() {
1058
		prefSize = null;
1059
		minSize = null;
1060
		clearLocations();
1061
		ellipseTextSize = null;
1062
		textSize = null;
1063
		subStringText = null;
1064
		if (iconInfo != null)
1065
			iconInfo.invalidate();
1066
		super.invalidate();
1067
	}
1068
1069
	/**
1070
	 * Returns <code>true</code> if the label's text is currently truncated
1071
	 * and is displaying an ellipsis, <code>false</code> otherwise.
1072
	 * 
1073
	 * @return <code>true</code> if the label's text is truncated
1074
	 * @since 2.0
1075
	 */
1076
	public boolean isTextTruncated() {
1077
		return !getSubStringTextSize().equals(getTextSize());
1078
	}
1079
1080
	/**
1081
	 * @see org.eclipse.draw2d.Figure#paintFigure(org.eclipse.draw2d.Graphics)
1082
	 */
1083
	public void paintFigure(Graphics graphics) {
1084
		if (isSelected()) {
1085
			graphics.pushState();
1086
			graphics.setBackgroundColor(ColorConstants.menuBackgroundSelected);
1087
			graphics.fillRectangle(getSelectionRectangle());
1088
			graphics.popState();
1089
			graphics.setForegroundColor(ColorConstants.white);
1090
		}
1091
		if (hasFocus()) {
1092
			graphics.pushState();
1093
			graphics.setXORMode(true);
1094
			graphics.setForegroundColor(ColorConstants.menuBackgroundSelected);
1095
			graphics.setBackgroundColor(ColorConstants.white);
1096
			graphics.drawFocus(getSelectionRectangle().resize(-1, -1));
1097
			graphics.popState();
1098
		}
1099
		if (isOpaque())
1100
			super.paintFigure(graphics);
1101
		Rectangle figBounds = getBounds();
1102
1103
		graphics.translate(figBounds.x, figBounds.y);
1104
		if (hasIcons())
1105
			paintIcons(graphics);
1106
1107
		String subString = getSubStringText();
1108
		if (subString.length() > 0) {
1109
			if (!isEnabled()) {
1110
				graphics.translate(1, 1);
1111
				graphics.setForegroundColor(ColorConstants.buttonLightest);
1112
				paintText(graphics, subString);
1113
				graphics.translate(-1, -1);
1114
				graphics.setForegroundColor(ColorConstants.buttonDarker);
1115
			} else {
1116
				paintText(graphics, subString);
1117
			}
1118
		}
1119
		graphics.translate(-figBounds.x, -figBounds.y);
1120
	}
1121
1122
	/**
1123
	 * Paints the text and optioanally underlines it
1124
	 * 
1125
	 * @param graphics The graphics context
1126
	 * @param subString The string to draw
1127
	 */	
1128
	private void paintText(Graphics graphics, String subString) {		
1129
		StringTokenizer tokenizer = new StringTokenizer(subString, "\n"); //$NON-NLS-1$
1130
		Font f = getFont();
1131
		FontMetrics fontMetrics = FigureUtilities.getFontMetrics(f);
1132
		int fontHeight = getMapMode().DPtoLP(fontMetrics.getHeight());
1133
		int fontHeightHalf = fontHeight / 2;
1134
		int textWidth = getTextExtents(subString, f, fontHeight).width;
1135
		Point p = getTextLocation();
1136
		int y = p.y;
1137
		int x = p.x;
1138
		final int wrapAlignment = getTextWrapAlignment();
1139
		boolean isUnderlined = isTextUnderlined();
1140
		boolean isStrikedThrough = isTextStrikedThrough();
1141
		Rectangle clipRect = new Rectangle();
1142
		graphics.getClip(clipRect);
1143
		int clipRectTopRight_x = clipRect.getTopRight().x;
1144
		// If the font's leading area is 0 then we need to add an offset to
1145
		// avoid truncating at the top (e.g. Korean fonts)
1146
		if (0 == fontMetrics.getLeading()) {
1147
			y +=  getMapModeConstants().nDPtoLP_2; // 2 is the leading area for default English			
1148
		}				
1149
1150
		while (tokenizer.hasMoreTokens()) {
1151
			String token = tokenizer.nextToken();
1152
			int tokenWidth = getTextExtents(token, f, fontHeight).width;
1153
			
1154
			switch (wrapAlignment) {
1155
				case CENTER:
1156
					x += (textWidth - tokenWidth) / 2;
1157
					break;
1158
				case RIGHT:
1159
					x += textWidth - tokenWidth;
1160
					break;
1161
			}
1162
			
1163
			// increase the clipping rectangle by a small amount to account for font overhang
1164
			// from italic / irregular characters etc.
1165
			
1166
			
1167
			if (tokenWidth + x <= clipRectTopRight_x) {
1168
				Rectangle newClipRect = new Rectangle(clipRect);
1169
				newClipRect.width += (tokenWidth / token.length()) / 2;
1170
				graphics.setClip(newClipRect);
1171
			}
1172
				
1173
			graphics.drawText(token, x, y);
1174
			graphics.setClip(clipRect);
1175
			
1176
			y += fontHeight;
1177
1178
			if (isUnderlined)
1179
				graphics.drawLine(x, y - 1, x + tokenWidth, y - 1);
1180
			if (isStrikedThrough)
1181
				graphics.drawLine(x, y - fontHeightHalf + 1, x + tokenWidth, y
1182
					- fontHeightHalf + 1);
1183
		}
1184
	}
1185
1186
	/**
1187
	 * Paints the icon(s)
1188
	 * 
1189
	 * @param graphics The graphics context
1190
	 */
1191
	private void paintIcons(Graphics graphics) {
1192
		Point p = Point.SINGLETON;
1193
		p.setLocation(getIconLocation());
1194
1195
		int num = getNumberofIcons();
1196
		for (int i = 0; i < num; i++) {
1197
			Image icon = getIcon(i); 
1198
			if (icon != null) {
1199
				graphics.drawImage(icon, p);
1200
				p.x += getIconSize(i).width;
1201
			}
1202
		}
1203
	}
1204
1205
	/**
1206
	 * Sets the label's icon to the passed image.
1207
	 * 
1208
	 * @param image the new label image
1209
	 * @since 2.0
1210
	 */
1211
	public void setIcon(Image image) {
1212
		setIcon(image, 0);
1213
	}
1214
1215
	/**
1216
	 * Sets the label's icon at given index
1217
	 * 
1218
	 * @param image The icon image or null to remove the icon
1219
	 * @param index The icon index
1220
	 */
1221
	public void setIcon(Image image, int index) {
1222
		if (iconInfo == null) {
1223
			if (index == 0) {
1224
				iconInfo = getMapModeConstants().getSingleIconInfo(image);
1225
			} else {
1226
				iconInfo = new MultiIconInfo();
1227
				iconInfo.setIcon(image, index);
1228
			}
1229
			revalidate();
1230
			repaint();// Call repaint, in case the image dimensions are the same.           
1231
		} else if (iconInfo.getIcon(index) != image) {
1232
			if (iconInfo.getMaxIcons() == 1) {
1233
				if (index == 0) {
1234
					iconInfo = getMapModeConstants().getSingleIconInfo(image);
1235
					revalidate();
1236
					repaint();// Call repaint, in case the image dimensions are the same.
1237
					return;
1238
				}
1239
				IconInfo oldIconInfo = iconInfo;
1240
				iconInfo = new MultiIconInfo();
1241
				iconInfo.setIcon(oldIconInfo.getIcon(0), 0);
1242
			}
1243
			iconInfo.setIcon(image, index);
1244
			revalidate();
1245
			repaint();// Call repaint, in case the image dimensions are the same.
1246
		}	
1247
	}
1248
1249
1250
	/**
1251
	 * Sets the icon alignment relative to the .abel's alignment to the passed
1252
	 * value. The default is {@link PositionConstants#CENTER}. Other possible
1253
	 * values are {@link PositionConstants#TOP},
1254
	 * {@link PositionConstants#BOTTOM},{@link PositionConstants#LEFT}and
1255
	 * {@link PositionConstants#RIGHT}.
1256
	 * 
1257
	 * @param align the icon alignment
1258
	 * @since 2.0
1259
	 */
1260
	public void setIconAlignment(int align) {
1261
		if (getIconAlignment() == align)
1262
			return;
1263
		setAlignmentFlags(align, FLAG_ICON_ALIGN);
1264
		clearLocations();
1265
		repaint();
1266
	}
1267
1268
	/**
1269
	 * getIconSize
1270
	 * @param index of icon to retrieve size of.
1271
	 * @return Dimension representing the icon size.
1272
	 */
1273
	protected Dimension getIconSize(int index) {
1274
		if (iconInfo == null)
1275
			return EMPTY_DIMENSION;
1276
		return iconInfo.getIconSize(getMapMode(), index);
1277
	}
1278
	
1279
	/**
1280
	 * getIconNumber
1281
	 * @return int number of icons in the wrap label
1282
	 */
1283
	protected int getNumberofIcons() {
1284
		if (iconInfo == null)
1285
			return 0;
1286
		return iconInfo.getNumberofIcons();
1287
	}
1288
	
1289
	/**
1290
	 * getTotalIconSize
1291
	 * Calculates the total union of icon sizes
1292
	 * @return Dimension that is the union of icon sizes
1293
	 */
1294
	protected Dimension getTotalIconSize() {
1295
		if (iconInfo == null)
1296
			return EMPTY_DIMENSION;
1297
		return iconInfo.getTotalIconSize(getMapMode());
1298
	}
1299
1300
	/**
1301
	 * Sets the Label's alignment to the passed value. The default is
1302
	 * {@link PositionConstants#CENTER}. Other possible values are
1303
	 * {@link PositionConstants#TOP},{@link PositionConstants#BOTTOM},
1304
	 * {@link PositionConstants#LEFT}and {@link PositionConstants#RIGHT}.
1305
	 * 
1306
	 * @param align label alignment
1307
	 */
1308
	public void setLabelAlignment(int align) {
1309
		if (getLabelAlignment() == align)
1310
			return;
1311
		setAlignmentFlags(align, FLAG_LABEL_ALIGN);
1312
		clearLocations();
1313
		repaint();
1314
	}
1315
1316
	/**
1317
	 * Return the ellipse string.
1318
	 * 
1319
	 * @return the <code>String</code> that represents the fact that the
1320
	 * text has been truncated and that more text is available but hidden. 
1321
	 * Usually this is represented by "...".
1322
	 */
1323
	protected String getEllipse() {
1324
		return _ellipse;
1325
	}
1326
	
1327
	/**
1328
	 * Sets the label's text.
1329
	 * 
1330
	 * @param s the new label text
1331
	 * @since 2.0
1332
	 */
1333
	public void setText(String s) {
1334
		//"text" will never be null.
1335
		if (s == null)
1336
			s = "";//$NON-NLS-1$
1337
		if (text.equals(s))
1338
			return;
1339
		text = s;
1340
		revalidate();
1341
		repaint(); //If the new text does not cause a new size, we still need
1342
		// to paint.
1343
	}
1344
1345
	/**
1346
	 * Sets the text alignment of the Label relative to the label alignment. The
1347
	 * default is {@link PositionConstants#CENTER}. Other possible values are
1348
	 * {@link PositionConstants#TOP},{@link PositionConstants#BOTTOM},
1349
	 * {@link PositionConstants#LEFT}and {@link PositionConstants#RIGHT}.
1350
	 * 
1351
	 * @param align the text alignment
1352
	 * @since 2.0
1353
	 */
1354
	public void setTextAlignment(int align) {
1355
		if (getTextAlignment() == align)
1356
			return;
1357
		setAlignmentFlags(align, FLAG_TEXT_ALIGN);
1358
		clearLocations();
1359
		repaint();
1360
	}
1361
1362
	/**
1363
	 * Sets the text placement of the label relative to its icon. The default is
1364
	 * {@link PositionConstants#EAST}. Other possible values are
1365
	 * {@link PositionConstants#NORTH},{@link PositionConstants#SOUTH}and
1366
	 * {@link PositionConstants#WEST}.
1367
	 * 
1368
	 * @param where the text placement
1369
	 * @since 2.0
1370
	 */
1371
	public void setTextPlacement(int where) {
1372
		if (getTextPlacement() == where)
1373
			return;
1374
		setPlacementFlags(where, FLAG_TEXT_PLACEMENT);
1375
		revalidate();
1376
		repaint();
1377
	}
1378
1379
	/**
1380
	 * Sets whether the label text should be underlined
1381
	 * 
1382
	 * @param b Wether the label text should be underlined
1383
	 */
1384
	public void setTextUnderline(boolean b) {
1385
		if (isTextUnderlined() == b)
1386
			return;
1387
		setFlag(FLAG_UNDERLINED, b);
1388
		repaint();
1389
	}
1390
1391
	/**
1392
	 * @return whether the label text is underlined
1393
	 */
1394
	public boolean isTextUnderlined() {
1395
		return (flags & FLAG_UNDERLINED) != 0;
1396
	}
1397
	
1398
	/**
1399
	 * Sets whether the label text should be striked-through
1400
	 * 
1401
	 * @param b Wether the label text should be stricked-through
1402
	 */
1403
	public void setTextStrikeThrough(boolean b) {
1404
		if (isTextStrikedThrough() == b)
1405
			return;
1406
		setFlag(FLAG_STRIKEDTHROUGH, b);
1407
		repaint();
1408
	}
1409
1410
	/**
1411
	 * @return wether the label text is stricked-through
1412
	 */
1413
	public boolean isTextStrikedThrough() {
1414
		return (flags & FLAG_STRIKEDTHROUGH) != 0;
1415
	}
1416
1417
	/**
1418
	 * Sets whether the label text should wrap
1419
	 * 
1420
	 * @param b whether the label text should wrap
1421
	 */
1422
	public void setTextWrap(boolean b) {
1423
		if (isTextWrapped() == b)
1424
			return;
1425
		setFlag(FLAG_WRAP, b);
1426
		revalidate();
1427
		repaint();
1428
	}
1429
1430
	/**
1431
	 * @return wether the label text wrap is on
1432
	 */
1433
	public boolean isTextWrapped() {
1434
		return (flags & FLAG_WRAP) != 0;
1435
	}
1436
1437
	/**
1438
	 * Sets the wrapping width of the label text. This is only valid if text
1439
	 * wrapping is turned on
1440
	 * 
1441
	 * @param i The label text wrapping width
1442
	 */
1443
	public void setTextWrapWidth(int i) {
1444
		/*
1445
		 * if (this.wrapWidth == i) return; this.wrapWidth = i; revalidate();
1446
		 * repaint();
1447
		 */
1448
	}
1449
1450
	/**
1451
	 * Sets the wrapping width of the label text. This is only valid if text
1452
	 * wrapping is turned on
1453
	 * 
1454
	 * @param i The label text wrapping width
1455
	 */
1456
	public void setTextWrapAlignment(int i) {
1457
		if (getTextWrapAlignment() == i)
1458
			return;
1459
		
1460
		setAlignmentFlags(i, FLAG_WRAP_ALIGN);
1461
		repaint();
1462
	}
1463
1464
	/**
1465
	 * @return the label text wrapping width
1466
	 */
1467
	public int getTextWrapAlignment() {
1468
		return getAlignment(FLAG_WRAP_ALIGN);
1469
	}
1470
	
1471
	/**
1472
	 * setPlacementFlags
1473
	 * @param align 
1474
	 * @param flagOffset
1475
	 */
1476
	private void setPlacementFlags(int align, int flagOffset) {
1477
		flags &= ~(0x7 * flagOffset);
1478
		switch (align) {
1479
			case EAST:
1480
				flags |= 0x1 * flagOffset;
1481
				break;
1482
			case WEST:
1483
				flags |= 0x2 * flagOffset;
1484
				break;
1485
			case NORTH:
1486
				flags |= 0x3 * flagOffset;
1487
				break;
1488
			case SOUTH:
1489
				flags |= 0x4 * flagOffset;
1490
				break;
1491
		}
1492
	}
1493
1494
	/**
1495
	 * getPlacement
1496
	 * 
1497
	 * @param flagOffset
1498
	 * @return PositionConstant representing the placement
1499
	 */
1500
	private int getPlacement(int flagOffset) {
1501
		int wrapValue = flags & (0x7 * flagOffset);
1502
		if (wrapValue == 0x1 * flagOffset)
1503
			return EAST;
1504
		else if (wrapValue == 0x2 * flagOffset)
1505
			return WEST;
1506
		else if (wrapValue == 0x3 * flagOffset)
1507
			return NORTH;
1508
		else if (wrapValue == 0x4 * flagOffset)
1509
			return SOUTH;
1510
		
1511
		return EAST;
1512
	}
1513
	
1514
	/**
1515
	 * setAlignmentFlags
1516
	 * @param align 
1517
	 * @param flagOffset
1518
	 */
1519
	private void setAlignmentFlags(int align, int flagOffset) {
1520
		flags &= ~(0x7 * flagOffset);
1521
		switch (align) {
1522
			case CENTER:
1523
				flags |= 0x1 * flagOffset;
1524
				break;
1525
			case TOP:
1526
				flags |= 0x2 * flagOffset;
1527
				break;
1528
			case LEFT:
1529
				flags |= 0x3 * flagOffset;
1530
				break;
1531
			case RIGHT:
1532
				flags |= 0x4 * flagOffset;
1533
				break;
1534
			case BOTTOM:
1535
				flags |= 0x5 * flagOffset;
1536
				break;
1537
		}
1538
	}
1539
1540
	/**
1541
	 * Retrieves the alignment value from the flags member.
1542
	 * 
1543
	 * @param flagOffset that is the bitwise value representing the offset.
1544
	 * @return PositionConstant representing the alignment
1545
	 */
1546
	private int getAlignment(int flagOffset) {
1547
		int wrapValue = flags & (0x7 * flagOffset);
1548
		if (wrapValue == 0x1 * flagOffset)
1549
			return CENTER;
1550
		else if (wrapValue == 0x2 * flagOffset)
1551
			return TOP;
1552
		else if (wrapValue == 0x3 * flagOffset)
1553
			return LEFT;
1554
		else if (wrapValue == 0x4 * flagOffset)
1555
			return RIGHT;
1556
		else if (wrapValue == 0x5 * flagOffset)
1557
			return BOTTOM;
1558
		
1559
		return CENTER;
1560
	}
1561
	
1562
1563
	/**
1564
	 * Sets the selection state of this label
1565
	 * 
1566
	 * @param b true will cause the label to appear selected
1567
	 */
1568
	public void setSelected(boolean b) {
1569
		if (isSelected() == b)
1570
			return;
1571
		setFlag(FLAG_SELECTED, b);
1572
		repaint();
1573
	}
1574
1575
	/**
1576
	 * @return the selection state of this label
1577
	 */
1578
	public boolean isSelected() {
1579
		return (flags & FLAG_SELECTED) != 0;
1580
	}
1581
1582
	/**
1583
	 * Sets the focus state of this label
1584
	 * 
1585
	 * @param b true will cause a focus rectangle to be drawn around the text
1586
	 *            of the Label
1587
	 */
1588
	public void setFocus(boolean b) {
1589
		if (hasFocus() == b)
1590
			return;
1591
		setFlag(FLAG_HASFOCUS, b);
1592
		repaint();
1593
	}
1594
1595
	/**
1596
	 * @return the focus state of this label
1597
	 */
1598
	public boolean hasFocus() {
1599
		return (flags & FLAG_HASFOCUS) != 0;
1600
	}
1601
1602
	/**
1603
	 * Returns the bounds of the text selection
1604
	 * 
1605
	 * @return The bounds of the text selection
1606
	 */
1607
	private Rectangle getSelectionRectangle() {
1608
		Rectangle figBounds = getTextBounds();
1609
		int expansion = getMapModeConstants().nDPtoLP_2;
1610
		figBounds.resize(expansion, expansion);
1611
		translateToParent(figBounds);
1612
		figBounds.intersect(getBounds());
1613
		return figBounds;
1614
	}
1615
1616
	/**
1617
	 * returns the position of last character within the supplied text that will
1618
	 * fit within the supplied width.
1619
	 * 
1620
	 * @param s a text string
1621
	 * @param f font used to draw the text string
1622
	 * @param w width in pixles.
1623
	 * @param fontHeight int <b>mapped already to logical units</b>.
1624
	 */
1625
	private int getLineWrapPosition(String s, Font f, int w, int fontHeight) {
1626
		if (getTextExtents(s, f, fontHeight).width <= w) {
1627
			return s.length();
1628
		}
1629
		// create an iterator for line breaking positions
1630
		BreakIterator iter = BreakIterator.getLineInstance();
1631
		iter.setText(s);
1632
		int start = iter.first();
1633
		int end = iter.next();
1634
1635
		// if the first line segment does not fit in the width,
1636
		// determine the position within it where we need to cut
1637
		if (getTextExtents(s.substring(start, end), f, fontHeight).width > w) {
1638
			iter = BreakIterator.getCharacterInstance();
1639
			iter.setText(s);
1640
			start = iter.first();
1641
		}
1642
1643
		// keep iterating as long as width permits
1644
		do
1645
			end = iter.next();
1646
		while (end != BreakIterator.DONE
1647
			&& getTextExtents(s.substring(start, end), f, fontHeight).width <= w);
1648
		return (end == BreakIterator.DONE) ? iter.last()
1649
			: iter.previous();
1650
	}	
1651
1652
	/**
1653
	 * Returns the largest substring of <i>s </i> in Font <i>f </i> that can be
1654
	 * confined to the number of pixels in <i>availableWidth <i>.
1655
	 * 
1656
	 * @param s the original string
1657
	 * @param f the font
1658
	 * @param w the available width
1659
	 * @param fontHeight int <b>mapped already to logical units</b>.
1660
	 * @param charAverageWidth int <b>mapped already to logical units</b>.
1661
	 * @return the largest substring that fits in the given width
1662
	 * @since 2.0
1663
	 */
1664
	private int getLargestSubstringConfinedTo(String s, Font f, int w, int fontHeight, int charAverageWidth) {		
1665
		float avg = charAverageWidth;
1666
		int min = 0;
1667
		int max = s.length() + 1;
1668
1669
		//The size of the current guess
1670
		int guess = 0, guessSize = 0;
1671
		while ((max - min) > 1) {
1672
			//Pick a new guess size
1673
			//	New guess is the last guess plus the missing width in pixels
1674
			//	divided by the average character size in pixels
1675
			guess = guess + (int) ((w - guessSize) / avg);
1676
1677
			if (guess >= max)
1678
				guess = max - 1;
1679
			if (guess <= min)
1680
				guess = min + 1;
1681
1682
			//Measure the current guess
1683
			guessSize = getTextExtents(s.substring(0, guess), f, fontHeight).width;
1684
1685
			if (guessSize < w)
1686
				//We did not use the available width
1687
				min = guess;
1688
			else
1689
				//We exceeded the available width
1690
				max = guess;
1691
		}
1692
		return min;
1693
	}
1694
1695
	/**
1696
	 * Gets the tex extent scaled to the mapping mode
1697
	 */
1698
	private Dimension getTextExtents(String s, Font f, int fontHeight) {
1699
		if (s.length() == 0) {
1700
			return getMapModeConstants().dimension_nDPtoLP_0;
1701
		} else {
1702
			// height should be set using the font height and the number of
1703
			// lines in the string			
1704
			Dimension d = FigureUtilities.getTextExtents(s, f);
1705
			IMapMode mapMode = getMapMode();
1706
			d.width = mapMode.DPtoLP(d.width);
1707
			d.height = fontHeight * new StringTokenizer(s, "\n").countTokens();//$NON-NLS-1$
1708
			return d;			
1709
		}
1710
	}
1711
1712
    
125
    
1713
	
126
    /**
1714
}
127
     * Not supported.
128
     * @deprecated
129
     *
130
     */
131
    public void setTextWrapWidth() {
132
        // deprecated method.
133
    }
134
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/figures/LabelWithTextLayout.java (+1924 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.figures;
13
14
import java.lang.ref.WeakReference;
15
import java.util.ArrayList;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.WeakHashMap;
19
20
import org.eclipse.draw2d.Figure;
21
import org.eclipse.draw2d.Graphics;
22
import org.eclipse.draw2d.IFigure;
23
import org.eclipse.draw2d.Label;
24
import org.eclipse.draw2d.PositionConstants;
25
import org.eclipse.draw2d.StackLayout;
26
import org.eclipse.draw2d.geometry.Dimension;
27
import org.eclipse.draw2d.geometry.Insets;
28
import org.eclipse.draw2d.geometry.Point;
29
import org.eclipse.draw2d.geometry.Rectangle;
30
import org.eclipse.draw2d.text.BlockFlow;
31
import org.eclipse.draw2d.text.BlockFlowLayout;
32
import org.eclipse.draw2d.text.FlowPage;
33
import org.eclipse.draw2d.text.PageFlowLayout;
34
import org.eclipse.draw2d.text.TextFragmentBox;
35
import org.eclipse.gmf.runtime.draw2d.ui.internal.mapmode.IMapModeHolder;
36
import org.eclipse.gmf.runtime.draw2d.ui.internal.text.FlowUtilitiesEx;
37
import org.eclipse.gmf.runtime.draw2d.ui.internal.text.ParagraphTextLayoutEx;
38
import org.eclipse.gmf.runtime.draw2d.ui.internal.text.SelectableTextFlow;
39
import org.eclipse.gmf.runtime.draw2d.ui.internal.text.SingleLineTextLayoutWithEllipses;
40
import org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode;
41
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
42
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
43
import org.eclipse.swt.graphics.Font;
44
import org.eclipse.swt.graphics.Image;
45
46
/**
47
 * An extended label that has the following extra features: <br>
48
 * 1. Allows selection, focus feedback, underlined
49
 * and striked-through text.
50
 * 2. Supports multiple icons.
51
 * 3. Enhanced layout functionality for placing icon(s) and text.
52
 * 4. Text can be word-wrapped or be truncated with ellipses. <p><br>
53
 * 
54
 * 
55
 * <b>EXPLANATION OF LAYOUTS</b><br>
56
 * 
57
 * This WrapLabel contains functionality to display many icons alongside text.
58
 * The following will describe how the layout of these icons and text
59
 * are done. <p><br>
60
 * 
61
 * 
62
 * <u>Using {@link #setTextPlacement(int)}:</u><p>
63
 * 
64
 * All icons that are set using {@link #setIcon(Image)} are placed horizontally
65
 * one after the other. The position of the text <i>relative</i> to icons
66
 * depends on {@link #setTextPlacement(int)}. If text placement is set to 
67
 * {@link PositionConstants#EAST}, then the icon(s) would be placed on the left
68
 * of the text. Similarly, if text placement is set to
69
 * {@link PositionConstants#WEST}, the icon(s) will be placed on the right of the 
70
 * text; {@link PositionConstants#NORTH} would put the icons below the text; and
71
 * {@link PositionConstants#SOUTH} would place the icons above the text. <p><br>
72
 * 
73
 * <u>Using {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}:</u><p>
74
 * 
75
 *  Use {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)} to 
76
 *  align the text and icons <i>relative</i> to each other for more dynamic control. 
77
 *  If the text placement is on the east or west of the icon(s) 
78
 *  (i.e. the icon(s) on the left or right of the text respectively), then only
79
 *  {@link PositionConstants#TOP}, {@link PositionConstants#CENTER}, and
80
 *  {@link PositionConstants#BOTTOM} can be used when calling 
81
 *  {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}. In this case,
82
 *  setting the text alignment to {@link PositionConstants#TOP} will make sure
83
 *  that the top of the text is aligned horizontally with the top of the icon(s) <i>if</i>
84
 *  the height of the total size of icon(s) is greater than the height of the text. Similarly,
85
 *  setting the text alignment to {@link PositionConstants#CENTER} will make sure 
86
 *  that the top of the text is aligned horizontally with the vertical center of the total
87
 *  size of icon(s) <i>if</i> the height of the total size of icon(s) is greater than 
88
 *  the height of the text. Also, setting the text alignment to 
89
 *  {@link PositionConstants#BOTTOM} will make sure 
90
 *  that the bottom of the text is aligned horizontally with the bottom of the total
91
 *  size of icon(s) <i>if</i> the height of the total size of icon(s) is greater than 
92
 *  the height of the text. <p>
93
 *  
94
 *  The other scenario is when the text placement is on the south or north of the icon(s) 
95
 *  (i.e. the icon(s) above or below the text respectively). If this is true, only
96
 *  {@link PositionConstants#LEFT}, {@link PositionConstants#CENTER}, and
97
 *  {@link PositionConstants#RIGHT} can be used when calling 
98
 *  {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}.  In this case,
99
 *  setting the text alignment to {@link PositionConstants#LEFT} will make sure
100
 *  that the left of the text is aligned vertically with the left of the icon(s) <i>if</i>
101
 *  the width of the total size of icon(s) is greater than the width of the text. Similarly,
102
 *  setting the text alignment to {@link PositionConstants#CENTER} will make sure 
103
 *  that the left of the text is aligned vertically with the horizontal center of the total
104
 *  size of icon(s) <i>if</i> the width of the total size of icon(s) is greater than 
105
 *  the width of the text. Also, setting the text alignment to 
106
 *  {@link PositionConstants#RIGHT} will make sure 
107
 *  that the right of the text is aligned vertically with the right of the total
108
 *  size of icon(s) <i>if</i> the width of the total size of icon(s) is greater than 
109
 *  the width of the text. <p>
110
 *  
111
 *  {@link #setIconAlignment(int)} works identically as {@link #setTextAlignment(int)},
112
 *  except the roles of text and icon(s) are switched in the above descriptions. <p><br>
113
 * 
114
 *  
115
 *  <u>Using {@link #setLabelAlignment(int)}:</u><p>
116
 *  
117
 *  The entire label, text and icons, can moved into different positions <i>vertically</i>.
118
 *  {@link PositionConstants#TOP}, {@link PositionConstants#CENTER}, and
119
 *  {@link PositionConstants#BOTTOM} places the text and icons (no matter how they are arranged
120
 *  relatively to each other) on the top, center, or bottom of the bounds of 
121
 *  LabelWithTextLayout. <p><br>
122
 *  
123
 *  
124
 *  <u>Using {@link #setTextHorizontalAlignment(int)}:</u><p>
125
 *  
126
 *  Use {@link #setTextHorizontalAlignment(int)} with {@link PositionConstants#LEFT}, 
127
 *  {@link PositionConstants#CENTER}, or {@link PositionConstants#RIGHT} to justify
128
 *  the text accordingly. The icon(s) will move with the text, so this can be
129
 *  considered a <i>horizontal</i> label alignment equivalent for 
130
 *  {@link #setLabelAlignment(int)}. <p><br>
131
 *  
132
 * 
133
 * WARNING: User-nested figures are not expected within this WrapLabel. <p>
134
 * 
135
 * Some code taken from the original WrapLabel by melaasar
136
 * <p>
137
 * 
138
 * @author satif <p>
139
 */
140
public class LabelWithTextLayout
141
    extends Label
142
    implements PositionConstants {
143
    
144
    private static final Map mapModeConstantsMap = new WeakHashMap();
145
146
    private static class MapModeConstants {
147
148
        private static final int MAX_IMAGE_INFO = 12;
149
150
        public final WeakReference mapModeRef;
151
152
        public final int nDPtoLP_3;
153
154
        public final int nDPtoLP_2;
155
156
        public final SingleIconInfo[] singleIconInfos = new SingleIconInfo[MAX_IMAGE_INFO];
157
158
        public MapModeConstants(IMapMode mapMode) {
159
            this.mapModeRef = new WeakReference(mapMode);
160
            nDPtoLP_2 = mapMode.DPtoLP(2);
161
            nDPtoLP_3 = mapMode.DPtoLP(3);
162
        }
163
164
        public SingleIconInfo getSingleIconInfo(Image image) {
165
            if (image == null) {
166
                return SingleIconInfo.NULL_INFO;
167
            }
168
            SingleIconInfo info;
169
            for (int i = 0; i < MAX_IMAGE_INFO; ++i) {
170
                info = singleIconInfos[i];
171
                if (info == null) {
172
                    info = new SingleIconInfo(image);
173
                    singleIconInfos[i] = info;
174
                    return info;
175
                }
176
                if (info.icon == image) {
177
                    return info;
178
                }
179
            }
180
            int index = SingleIconInfo.count % MAX_IMAGE_INFO;
181
            info = new SingleIconInfo(image);
182
            singleIconInfos[index] = info;
183
            return info;
184
        }
185
    }
186
187
    private static abstract class IconInfo {
188
        /**
189
         * Gets the icon at the index location.
190
         * 
191
         * @param i
192
         *            the index to retrieve the icon of
193
         * @return <code>Image</code> that corresponds to the given index.
194
         */
195
        public abstract Image getIcon(int i);
196
        
197
        /**
198
         * Gets the icon size of the icon at the given index.
199
         * 
200
         * @param i
201
         * @return the <code>Dimension</code> that is the size of the icon at
202
         *         the given index.
203
         */
204
        public abstract Dimension getIconSize(IMapMode mapMode, int i);
205
206
        /**
207
         * @return the number of icons
208
         */
209
        public abstract int getNumberofIcons();
210
        
211
        /**
212
         * @return the <code>Dimension</code> that is the total size of all
213
         *         the icons.
214
         */
215
        public abstract Dimension getTotalIconSize(IMapMode mapMode);
216
217
        public abstract void invalidate();
218
        
219
        /**
220
         * Sets the icon at the index location.
221
         * 
222
         * @param icon
223
         * @param i
224
         */
225
        public abstract void setIcon(Image icon, int i);
226
        
227
        /**
228
         * 
229
         */
230
        public abstract int getMaxIcons();
231
232
    }   
233
234
    private static class SingleIconInfo
235
        extends IconInfo {  
236
237
        static int count;
238
        
239
        public static final SingleIconInfo NULL_INFO = new SingleIconInfo(){
240
            public int getNumberofIcons() {
241
                return 0;
242
            }
243
        };
244
245
        final Image icon;
246
247
        /** total icon size */
248
        private Dimension totalIconSize;
249
250
        private SingleIconInfo() {
251
            icon = null;//don't increment count, used only for NULL_INFO
252
        }
253
254
        public SingleIconInfo(Image icon) {
255
            this.icon = icon;
256
            ++count;
257
        }
258
259
        public final int getMaxIcons() {
260
            return 1;
261
        }
262
263
        
264
        public Image getIcon(int i) {
265
            if (i == 0) {
266
                return icon;
267
            } else if (i > 0) {
268
                return null;
269
            }
270
            throw new IndexOutOfBoundsException();
271
        }
272
273
        
274
        public void setIcon(Image img, int i) {
275
            throw new UnsupportedOperationException();
276
        }
277
278
        
279
        public Dimension getIconSize(IMapMode mapMode, int i) {
280
            if (i == 0) {
281
                return getTotalIconSize(mapMode);
282
            }
283
284
            throw new IndexOutOfBoundsException();
285
        }
286
287
        
288
        public int getNumberofIcons() {
289
            return 1;
290
        }
291
292
        
293
        public Dimension getTotalIconSize(IMapMode mapMode) {
294
            if (totalIconSize != null)
295
                return totalIconSize;
296
297
            if (icon != null && !icon.isDisposed()) {
298
                org.eclipse.swt.graphics.Rectangle imgBounds = icon.getBounds();
299
                totalIconSize = new Dimension(mapMode.DPtoLP(imgBounds.width),
300
                    mapMode.DPtoLP(imgBounds.height));
301
            } else {
302
                totalIconSize = EMPTY_DIMENSION;
303
            }
304
305
            return totalIconSize;
306
        }
307
308
        
309
        public void invalidate() {
310
            totalIconSize = null;
311
        }
312
313
    }
314
315
    private static class MultiIconInfo
316
        extends IconInfo {
317
318
        /** the label icons */
319
        private ArrayList icons = new ArrayList(2);
320
321
        /** total icon size */
322
        private Dimension totalIconSize;
323
324
        public MultiIconInfo() {
325
            super();
326
        }
327
328
        public int getMaxIcons() {
329
            return -1;
330
        }
331
332
        /**
333
         * Gets the icon at the index location.
334
         * 
335
         * @param i
336
         *            the index to retrieve the icon of
337
         * @return <code>Image</code> that corresponds to the given index.
338
         */
339
        public Image getIcon(int i) {
340
            if (i >= icons.size())
341
                return null;
342
343
            return (Image) icons.get(i);
344
        }
345
346
        /**
347
         * Sets the icon at the index location.
348
         * 
349
         * @param icon
350
         * @param i
351
         */
352
        public void setIcon(Image icon, int i) {
353
            int size = icons.size();
354
            if (i >= size) {
355
                for (int j = size; j < i; j++)
356
                    icons.add(null);
357
                icons.add(icon);
358
                icons.trimToSize();
359
            } else
360
                icons.set(i, icon);
361
        }
362
363
        /**
364
         * Gets the icon size of the icon at the given index.
365
         * 
366
         * @param i
367
         * @return the <code>Dimension</code> that is the size of the icon at
368
         *         the given index.
369
         */
370
        public Dimension getIconSize(IMapMode mapMode, int i) {
371
            Image img = getIcon(i);
372
            if (img != null && !img.isDisposed()) {
373
                org.eclipse.swt.graphics.Rectangle imgBounds = img.getBounds();             
374
                return new Dimension(mapMode.DPtoLP(imgBounds.width), mapMode
375
                    .DPtoLP(imgBounds.height));
376
            }
377
            return EMPTY_DIMENSION;
378
        }
379
380
        /**
381
         * @return the number of icons
382
         */
383
        public int getNumberofIcons() {
384
            return icons.size();
385
        }
386
387
        /**
388
         * @return the <code>Dimension</code> that is the total size of all
389
         *         the icons.
390
         */
391
        public Dimension getTotalIconSize(IMapMode mapMode) {
392
            if (totalIconSize != null)
393
                return totalIconSize;
394
            int iconNum = getNumberofIcons();
395
            if (iconNum == 0) {
396
                return totalIconSize = EMPTY_DIMENSION;
397
            }
398
399
            totalIconSize = new Dimension();
400
            for (int i = 0; i < iconNum; i++) {
401
                Dimension iconSize = getIconSize(mapMode, i);
402
                totalIconSize.width += iconSize.width;
403
                if (iconSize.height > totalIconSize.height)
404
                    totalIconSize.height = iconSize.height;
405
            }
406
407
            return totalIconSize;
408
        }
409
410
        /**
411
         * 
412
         */
413
        public void invalidate() {
414
            totalIconSize = null;
415
        }
416
    }
417
    
418
    
419
    /**
420
     * Lays out children according to the offset bounds provided.
421
     * This offset bounds is added to the container figure's client
422
     * area and applied to all figures. This allows the positioning
423
     * of all child figures in a stack layout but in a specific position
424
     * and with a specific dimension. 
425
     * 
426
     */
427
    protected static class OffsetLayout
428
        extends StackLayout {
429
430
        // the offset constraint to apply to child figures.
431
        private Rectangle offsetConstraint = null;
432
433
        /* (non-Javadoc)
434
         * @see org.eclipse.draw2d.StackLayout#layout(org.eclipse.draw2d.IFigure)
435
         */
436
        public void layout(IFigure figure) {
437
            List children = figure.getChildren();
438
            IFigure child;
439
            for (int i = 0; i < children.size(); i++) {
440
                child = (IFigure) children.get(i);
441
                Rectangle r = figure.getClientArea().getCopy();
442
                if (offsetConstraint != null) {
443
                    r.x += offsetConstraint.x;
444
                    r.y += offsetConstraint.y;
445
446
                    r.width += offsetConstraint.width;
447
                    r.height += offsetConstraint.height;
448
449
                    // we don't want the width and height to be 
450
                    // less than zero.
451
                    if (r.width < 0)
452
                        r.width = 0;
453
                    if (r.height < 0)
454
                        r.height = 0;
455
                }
456
457
                child.setBounds(r);
458
            }
459
        }
460
        
461
        /**
462
         * Set the offset constraint. <p>
463
         * 
464
         * The rectangle parameter represents an offset
465
         * that will be added to the container figure's client area
466
         * and this will be applied to all child figures.
467
         * 
468
         * @param offsetConstraint the offset constraint rectangle.
469
         */
470
        public void setOffsetConstraint(Rectangle offsetConstraint) {
471
            this.offsetConstraint = offsetConstraint;
472
        }
473
    }
474
475
    private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0);
476
    
477
    /**
478
     * calculateNow is a flag to indicate whether the layout data should be
479
     * recalculated before the Label is painted. This avoids recalculation
480
     * of layout data upon multiple paintFigure calls when the figure has not
481
     * been invalidated.
482
     */
483
    private static int FLAG_CALCULATE_LOCATIONS = MAX_FLAG << 4;
484
485
    /**
486
     * @see #setTextWrap(boolean)
487
     */
488
    private static int FLAG_WRAP = MAX_FLAG << 5;
489
490
    /**
491
     * @see #setTextAlignment(int)
492
     */
493
    private static int FLAG_TEXT_ALIGN = MAX_FLAG << 6;
494
495
    /**
496
     * @see #setIconAlignment(int)
497
     */
498
    private static int FLAG_ICON_ALIGN = MAX_FLAG << 12;
499
500
    /**
501
     * @see #setLabelAlignment(int)
502
     */
503
    private static int FLAG_LABEL_ALIGN = MAX_FLAG << 15;
504
505
    /**
506
     * @see #setTextPlacement(int)
507
     */
508
    private static int FLAG_TEXT_PLACEMENT = MAX_FLAG << 18;
509
    
510
    
511
    // cache the map mode constants...
512
    private MapModeConstants mapModeConstants;
513
514
    private IconInfo iconInfo;
515
516
    private SelectableTextFlow textFlow;
517
    
518
    private BlockFlow blockFlow;    
519
520
    private FlowPage flowPage;
521
522
    // cached location of the icon.
523
    private Point iconLocation;     
524
525
    /**
526
     * textBounds is used as the offsetConstraint in OffsetLayout,
527
     * primarily to position flowPage correctly to align it with the
528
     * icon(s)
529
     */
530
    private Rectangle textBounds;
531
532
    /**
533
     * Cache the preferred width and height parameters. getPreferredSize can get
534
     * called multiple times with the same parameters, so cache these to avoid
535
     * redundant recalculations.
536
     */
537
    private int cached_PrefWidth = Integer.MIN_VALUE,
538
            cached_PrefHeight = Integer.MIN_VALUE;
539
    
540
    /**
541
     * Construct a Label with no text and no icon.
542
     *
543
     */
544
    public LabelWithTextLayout() {
545
        super();
546
547
        this.setLayoutManager(new OffsetLayout());
548
        
549
        setupTextFigures(false);
550
551
        // default flags...
552
        setAlignmentFlags(CENTER, FLAG_TEXT_ALIGN);
553
        setAlignmentFlags(CENTER, FLAG_ICON_ALIGN);
554
        setAlignmentFlags(CENTER, FLAG_LABEL_ALIGN);
555
        blockFlow.setHorizontalAligment(LEFT);
556
        
557
        setPlacementFlags(EAST, FLAG_TEXT_PLACEMENT);
558
        setFlag(FLAG_WRAP, false);
559
    }
560
561
    /**
562
     * Construct a Label with passed Image as its icon.
563
     * 
564
     * @param i
565
     *            the label image
566
     */
567
    public LabelWithTextLayout(Image i) {
568
        this();
569
        iconInfo = new SingleIconInfo(i);
570
    }
571
572
    /**
573
     * Construct a Label with passed String as the Label's text.
574
     * @param s the String to set as the Label's text.
575
     */
576
    public LabelWithTextLayout(String s) {
577
        this();
578
        textFlow.setText(s);
579
    }
580
581
    /**
582
     * Construct a Label with passed String as text and passed Image as its
583
     * icon.
584
     * 
585
     * @param s
586
     *            the label text
587
     * @param i
588
     *            the label image
589
     */
590
    public LabelWithTextLayout(String s, Image i) {
591
        this();
592
        textFlow.setText(s);
593
        iconInfo = new SingleIconInfo(i);
594
        
595
    }
596
597
    /**
598
     * Calculates the text alignment relative to the icon.<br/> NOTE: Should be
599
     * called by calculateLocations only.
600
     * <p>
601
     * 
602
     * @param textPlacement
603
     *            the text placement relative to the icon.
604
     * @param totalIconSize
605
     *            the total size of all icons.
606
     * @param literalTextSize
607
     *            the size of the text.
608
     * 
609
     */
610
    private void calculateIconAlignment(int textPlacement,
611
            Dimension totalIconSize, Rectangle _literalTextBounds) {
612
        switch (textPlacement) {
613
            case NORTH:
614
            case SOUTH:
615
                if (totalIconSize.width < _literalTextBounds.width) {
616
                    switch (getIconAlignment()) {
617
                        case LEFT:
618
                            // nothing to add
619
                            break;
620
                        case CENTER:
621
                            iconLocation.x += _literalTextBounds.x + 
622
                                (_literalTextBounds.width - totalIconSize.width) / 2;
623
                            break;
624
                        case RIGHT:
625
                            iconLocation.x += _literalTextBounds.x + 
626
                                (_literalTextBounds.width - totalIconSize.width);
627
                            break;
628
                    }
629
                }
630
                break;
631
            case WEST:
632
            case EAST:
633
                if (totalIconSize.height < _literalTextBounds.height) {
634
                    switch (getIconAlignment()) {
635
                        case TOP:
636
                            // nothing to add
637
                            break;
638
                        case CENTER:
639
                            iconLocation.y = (_literalTextBounds.height - totalIconSize.height) / 2;
640
                            break;
641
                        case BOTTOM:
642
                            iconLocation.y = (_literalTextBounds.height - totalIconSize.height);
643
                            break;
644
                    }
645
                }
646
                break;
647
        }
648
    }
649
650
    /**
651
     * Calculates the text alignment relative to the icon.<br/> NOTE: Should be
652
     * called by calculateLocations only.
653
     * <p>
654
     * 
655
     * @param textPlacement
656
     *            the text placement relative to the icon.
657
     * @param totalIconSize
658
     *            the total size of all icons.
659
     * @param literalTextSize
660
     *            the size of the text.
661
     */
662
    private void calculateTextAlignment(int textPlacement,
663
            Dimension totalIconSize, Dimension literalTextSize) {
664
        switch (textPlacement) {
665
            case NORTH:
666
            case SOUTH:
667
                if (literalTextSize.width < totalIconSize.width) {
668
                    switch (getTextAlignment()) {
669
                        case LEFT:
670
                            // nothing to add
671
                            break;
672
                        case CENTER:
673
                            textBounds.x += (totalIconSize.width - literalTextSize.width) / 2;
674
                            break;
675
                        case RIGHT:
676
                            textBounds.x += (totalIconSize.width - literalTextSize.width);
677
                            break;
678
                    }
679
                }
680
                break;
681
            case WEST:
682
            case EAST:
683
                if (literalTextSize.height < totalIconSize.height) {
684
                    switch (getTextAlignment()) {
685
                        case TOP:
686
                            // nothing to add
687
                            break;
688
                        case CENTER:
689
                            textBounds.y += (totalIconSize.height - literalTextSize.height) / 2;
690
                            break;
691
                        case BOTTOM:
692
                            textBounds.y += (totalIconSize.height - literalTextSize.height);
693
                            break;
694
                    }
695
                }
696
                break;
697
        }
698
    }
699
700
    /**
701
     * Sets up the layout of text and icons.
702
     * 
703
     */
704
    private void calculateLocations() {
705
706
        int textPlacement = getTextPlacement();
707
        Dimension totalIconSize = getTotalIconSize();
708
        Rectangle literalSubstringTextSize = textFlow.getLiteralSubstringTextBounds();
709
710
        layoutTextAndIcons(textPlacement, totalIconSize, getPreferredSize(
711
            cached_PrefWidth, -1));
712
713
        calculateIconAlignment(textPlacement, totalIconSize,
714
            literalSubstringTextSize);
715
        calculateTextAlignment(textPlacement, totalIconSize,
716
            literalSubstringTextSize.getSize());
717
        
718
        if (getLayoutManager() instanceof OffsetLayout) {
719
            ((OffsetLayout)getLayoutManager()).setOffsetConstraint(textBounds);
720
        }
721
        
722
        this.layout();
723
        flowPage.getLayoutManager().layout(textFlow);
724
        
725
        finalIconAdjustments(textPlacement, literalSubstringTextSize.getSize());
726
    }
727
728
    /**
729
     * Returns the bounds of the label's text. Note that the bounds are calculated using the 
730
     * label's text AFTER it has been laid out by the TextLayoutManager. <p>
731
     * 
732
     * If you want to know the size of the text without any layout style applied,
733
     * use {@link #getTextSize()}
734
     * 
735
     * @return the bounds of this label's laid-out text
736
     */
737
    public Rectangle getTextBounds() {
738
        calculateLocations();
739
        
740
        return calculateTextBounds(false);
741
    }
742
743
    /* (non-Javadoc)
744
     * @see org.eclipse.draw2d.Label#getIconBounds()
745
     */
746
    public Rectangle getIconBounds() {
747
        if (iconLocation == null)
748
            calculateLocations();
749
750
        Point icon_Location = this.iconLocation.getCopy();
751
        translateToParent(icon_Location);
752
        Rectangle iconBounds = new Rectangle(icon_Location, getTotalIconSize());
753
        return iconBounds;
754
    }
755
756
    /**
757
     * Returns the gap between the text and the icon(s).
758
     * 
759
     * @return the gap, or 0 if there is no icon or text.
760
     */
761
    public int getIconTextGap() {
762
        if (iconInfo == null || getText().equals("") || getIcon() == null) //$NON-NLS-1$
763
            return 0;
764
        return getMapModeConstants().nDPtoLP_3;
765
    }
766
767
    /**
768
     * Get the text of only the visible area.
769
     */
770
    protected Dimension getSubStringTextSize() {
771
        return textFlow.getLiteralSubstringTextBounds().getSize();
772
    }
773
774
    /*
775
     * (non-Javadoc)
776
     * 
777
     * @see org.eclipse.draw2d.Label#getTextLocation()
778
     */
779
    protected Point getTextLocation() {
780
        return getTextBounds().getLocation();
781
    }
782
783
    /**
784
     * not yet supported. Default is to return the entire text.
785
     * 
786
     * @see WrapLabel#getText()
787
     */
788
    public String getSubStringText() {
789
        return getText();
790
    }
791
792
    /**
793
     * Returns the visible-only or total text size from whatever
794
     * the current state of the fragments in textFlow are. <p>
795
     * 
796
     * These bounds represents the bounds of the text and text
797
     * only. However, note that since flowPage can be translated by
798
     * the OffsetLayout, these will not be the same as the one given
799
     * by {@link SelectableTextFlow#getLiteralSubstringTextBounds()}. <p>
800
     * 
801
     * These bounds exclude any area which the text does not occupy.
802
     * 
803
     * @param subStringText
804
     *            true to stop calculating at non-visible text area.
805
     */
806
    private Rectangle calculateTextBounds(boolean subStringText) {
807
808
        Rectangle refBounds = getBounds(), calRect = new Rectangle(
809
            Integer.MAX_VALUE, Integer.MAX_VALUE, 0, 0);
810
811
        if (textFlow == null || blockFlow == null || flowPage == null)
812
            return calRect;
813
814
        List fragments = textFlow.getFragments();
815
816
        int locY = 0;
817
818
        if (textBounds != null)
819
            locY = textBounds.y;
820
821
        for (int i = 0; i < fragments.size(); i++) {
822
            TextFragmentBox fragment = (TextFragmentBox) fragments.get(i);
823
824
            if (fragment.getLineRoot() == null)
825
                continue;
826
827
            int y = fragment.getLineRoot().getVisibleTop();
828
829
            if (fragment.getX() < calRect.x)
830
                calRect.x = fragment.getX();
831
            if (y < calRect.y)
832
                calRect.y = y;
833
834
            if (calRect.width < fragment.getWidth())
835
                calRect.width = fragment.getWidth();
836
837
            y = fragment.getAscent() + fragment.getDescent();
838
            calRect.height += y;
839
840
            // we don't want to continue to non-visible fragments,
841
            // resulting in performance improvement
842
            if (subStringText && calRect.height + locY > refBounds.height) {
843
                if (subStringText) // calculate only substring text.
844
                    break;
845
            }
846
        }
847
848
        if (!subStringText) {
849
            calRect.resize(getMapModeConstants().nDPtoLP_2, 
850
                getMapModeConstants().nDPtoLP_2);
851
852
            blockFlow.translateToParent(calRect);
853
            flowPage.translateToParent(calRect);
854
            translateToParent(calRect);
855
        }
856
        
857
        return calRect;
858
    }
859
860
    /**
861
     * Calculates the placement of the text relative to the icon. <br>
862
     * NOTE: to be used by calculateLocations only.<br>
863
     * @see #getTextPlacement() <br>
864
     * 
865
     * <p>
866
     * 
867
     * @param insertionPoint
868
     *            the <code>Point</code> where the text and icon have to be
869
     *            inserted.
870
     * @param textPlacement
871
     *            the placement of text relative to the icon.
872
     * @param totalIconSize
873
     *            the total size of all icons.
874
     */
875
    private void calculateTextPlacement(Point insertionPoint,
876
            int textPlacement, Dimension totalIconSize) {
877
        switch (textPlacement) {
878
            case NORTH:
879
                textBounds = new Rectangle(new Point(insertionPoint.x,
880
                    insertionPoint.y), new Dimension(-insertionPoint.x,
881
                    -insertionPoint.y));
882
                iconLocation = new Point(insertionPoint.x,
883
                    textFlow.getSize().height + getIconTextGap()
884
                        + textFlow.getDescent());
885
                
886
                if (textBounds.x < 0)
887
                    textBounds.x = 0;
888
                if (textBounds.y < 0)
889
                    textBounds.y = 0;
890
                
891
                break;
892
            case SOUTH:
893
                textBounds = new Rectangle(new Point(insertionPoint.x,
894
                    totalIconSize.height + getIconTextGap()), new Dimension(
895
                    -insertionPoint.x,
896
                    -(totalIconSize.height + getIconTextGap())));
897
                iconLocation = new Point(insertionPoint.x, insertionPoint.y);
898
                
899
                if (textBounds.x < 0)
900
                    textBounds.x = 0;
901
                
902
                break;
903
            case WEST:
904
                textBounds = new Rectangle(new Point(insertionPoint.x,
905
                    insertionPoint.y), new Dimension(
906
                    -(totalIconSize.width + getIconTextGap() + insertionPoint.x),
907
                    -insertionPoint.y));
908
                iconLocation = new Point(getSize().width - totalIconSize.width,
909
                    insertionPoint.y);
910
                
911
                if (textBounds.x < 0)
912
                    textBounds.x = 0;
913
                if (textBounds.y < 0)
914
                    textBounds.y = 0;
915
                
916
                break;
917
            case EAST:
918
                iconLocation = new Point(insertionPoint.x, insertionPoint.y);
919
                textBounds = new Rectangle(
920
                    new Point(insertionPoint.x + totalIconSize.width
921
                        + getIconTextGap(), insertionPoint.y),
922
                    new Dimension(
923
                        -(totalIconSize.width + getIconTextGap() + insertionPoint.x),
924
                        -insertionPoint.y));
925
                
926
                if ((textBounds.y < 0) || (textBounds.y > getBounds().height))
927
                    textBounds.y = 0;
928
                
929
                break;
930
        }
931
    }
932
933
    /**
934
     * Clears all cached layout data. Will enable layout recalculation next time
935
     * anything is validated or when paintFigure is called.
936
     * 
937
     */
938
    private void clearLocations() {
939
        prefSize = null;
940
        minSize = null;
941
        iconLocation = null;
942
        textBounds = null;
943
        setFlag(FLAG_CALCULATE_LOCATIONS, true);
944
    }
945
946
    /**
947
     * Executes the case exceptions/constraints for icon alignment.<br>
948
     * NOTE: to be used by calculateLocations only.
949
     * <p>
950
     * 
951
     * @param textPlacement
952
     *            the text placement (N/S/E/W)
953
     * @param literalTextSize
954
     *            the substring text size.
955
     */
956
    private void finalIconAdjustments(int textPlacement, Dimension literalTextSize) {
957
        // the range checks below are to make sure we haven't calculated
958
        // anything that can't be displayed...
959
        int textHorizontalAlignment = getTextHorizontalAlignment();
960
961
        switch (textPlacement) {
962
            case EAST:
963
                // we want the icon to be right next to the text all the time...
964
                if (textHorizontalAlignment == CENTER) {
965
                    iconLocation.x = (blockFlow.getBounds().width / 2)
966
                        - (literalTextSize.width / 2) - getIconTextGap();
967
                } else if (textHorizontalAlignment == RIGHT) {
968
                    iconLocation.x = blockFlow.getBounds().width
969
                        - literalTextSize.width - getIconTextGap();
970
                }
971
                
972
                if (iconLocation.x < 0)
973
                    iconLocation.x = 0;
974
                if (iconLocation.y < 0)
975
                    iconLocation.y = 0;
976
                break;
977
            case WEST:
978
                // we want the icon to be right next to the text all the time...
979
                if (textHorizontalAlignment == LEFT) {
980
                    iconLocation.x = literalTextSize.width + getIconTextGap();
981
                } else if (textHorizontalAlignment == CENTER) {
982
                    iconLocation.x = (flowPage.getBounds().width / 2)
983
                        + (literalTextSize.width / 2) + getIconTextGap();
984
                }
985
986
                if (iconLocation.y < 0)
987
                    iconLocation.y = 0;
988
                break;
989
            case NORTH:
990
                if (iconLocation.x < 0)
991
                    iconLocation.x = 0;
992
                break;
993
            case SOUTH:
994
                if (iconLocation.x < 0)
995
                    iconLocation.x = 0;
996
                if (iconLocation.y < 0)
997
                    iconLocation.y = 0;
998
                break;
999
        }
1000
    }
1001
1002
    /**
1003
     * Retrieves the alignment value from the flags member.
1004
     * 
1005
     * @param flagOffset
1006
     *            that is the bitwise value representing the offset.
1007
     * @return PositionConstant representing the alignment
1008
     */
1009
    private int getAlignment(int flagOffset) {
1010
        int wrapValue = flags & (0x7 * flagOffset);
1011
        if (wrapValue == 0x1 * flagOffset)
1012
            return CENTER;
1013
        else if (wrapValue == 0x2 * flagOffset)
1014
            return TOP;
1015
        else if (wrapValue == 0x3 * flagOffset)
1016
            return LEFT;
1017
        else if (wrapValue == 0x4 * flagOffset)
1018
            return RIGHT;
1019
        else if (wrapValue == 0x5 * flagOffset)
1020
            return BOTTOM;
1021
1022
        return CENTER;
1023
    }
1024
1025
    /**
1026
     * Return the ellipse string.
1027
     * 
1028
     * @return the <code>String</code> that represents the fact that the text
1029
     *         has been truncated and that more text is available but hidden.
1030
     *         Usually this is represented by "...".
1031
     */
1032
    protected String getEllipse() {
1033
        return FlowUtilitiesEx.ELLIPSIS;
1034
    }
1035
1036
    /**
1037
     * Returns the Label's icon.
1038
     * 
1039
     * @return the label icon
1040
     */
1041
    public Image getIcon() {
1042
        return getIcon(0);
1043
    }
1044
1045
    /**
1046
     * Returns the label's icon at the given index (provided an icon exists at
1047
     * this index).
1048
     * <p>
1049
     * 
1050
     * @param index
1051
     * @return the icon at the given index.
1052
     */
1053
    public Image getIcon(int index) {
1054
        if (iconInfo == null)
1055
            return null;
1056
        return iconInfo.getIcon(index);
1057
    }
1058
1059
    /**
1060
     * Returns the current alignment of the Label's icon. The default is
1061
     * {@link PositionConstants#CENTER}.
1062
     * <p>
1063
     * 
1064
     * @return the icon alignment
1065
     */
1066
    public int getIconAlignment() {
1067
        return getAlignment(FLAG_ICON_ALIGN);
1068
    }
1069
1070
    /*
1071
     * (non-Javadoc)
1072
     * 
1073
     * @see org.eclipse.draw2d.Label#getIconLocation()
1074
     */
1075
    protected Point getIconLocation() {
1076
        if (iconLocation == null)
1077
            calculateLocations();
1078
        else
1079
            finalIconAdjustments(getTextPlacement(), 
1080
                textFlow.getLiteralSubstringTextBounds().getSize());
1081
        return iconLocation;
1082
    }
1083
1084
    /**
1085
     * Returns the size of the icon at a given index.
1086
     * 
1087
     * @param index
1088
     *            of icon to retrieve size of.
1089
     * @return Dimension representing the icon size.
1090
     */
1091
    protected Dimension getIconSize(int index) {
1092
        if (iconInfo == null)
1093
            return EMPTY_DIMENSION;
1094
        return iconInfo.getIconSize(getFigureMapMode(), index);
1095
    }
1096
1097
    /**
1098
     * Returns the current alignment of the entire Label. The default label
1099
     * alignment is {@link PositionConstants#LEFT}.
1100
     * 
1101
     * @return the label alignment
1102
     */
1103
    public int getLabelAlignment() {
1104
        return getAlignment(FLAG_LABEL_ALIGN);
1105
    }
1106
1107
    /**
1108
     * @return <code>IMapMode</code> used by this figure.
1109
     *         <code>IMapMode</code> that allows for the coordinate mapping
1110
     *         from device to logical units.
1111
     */
1112
    private IMapMode getFigureMapMode() {
1113
        return (IMapMode) getMapModeConstants().mapModeRef.get();
1114
    }
1115
1116
    /**
1117
     * Returns the stored map mode constants.
1118
     * @return MapModeConstants that are cached.
1119
     */
1120
    private MapModeConstants getMapModeConstants() {
1121
        if (mapModeConstants == null) {
1122
            IMapMode mapMode = MapModeUtil.getMapMode(this);
1123
            while (mapMode instanceof IMapModeHolder) {
1124
                mapMode = ((IMapModeHolder) mapMode).getMapMode();
1125
            }
1126
            mapModeConstants = (MapModeConstants) mapModeConstantsMap
1127
                .get(mapMode);
1128
            if (mapModeConstants == null) {
1129
                mapModeConstants = new MapModeConstants(mapMode);
1130
                mapModeConstantsMap.put(mapMode, mapModeConstants);
1131
            }
1132
            if (textFlow != null) {
1133
                ((TextLayoutManagerWithMapMode)
1134
                        textFlow.getLayoutManager()).setMapMode(mapMode);
1135
                textFlow.setMapMode(mapMode);
1136
            }
1137
        }
1138
        return mapModeConstants;
1139
    }
1140
1141
    /*
1142
     * (non-Javadoc)
1143
     * 
1144
     * @see org.eclipse.draw2d.Label#calculateLabelSize(org.eclipse.draw2d.geometry.Dimension)
1145
     */
1146
    protected Dimension calculateLabelSize(Dimension txtSize) {      
1147
        if (txtSize.equals(EMPTY_DIMENSION))
1148
            return txtSize; // return 0,0 dimension.
1149
        
1150
        int gap = getIconTextGap();
1151
        Dimension d = new Dimension(0, 0);
1152
        switch (getTextPlacement()) {
1153
            case WEST:
1154
            case EAST:
1155
                d.width = getTotalIconSize().width + gap + txtSize.width;
1156
                d.height = Math.max(getTotalIconSize().height, txtSize.height);
1157
                break;
1158
            default: // NORTH or SOUTH
1159
                d.width = Math.max(getTotalIconSize().width, txtSize.width);
1160
                d.height = getTotalIconSize().height + gap + txtSize.height;
1161
                break;
1162
        }
1163
        
1164
        return d;
1165
    } 
1166
1167
    /*
1168
     * (non-Javadoc)
1169
     * 
1170
     * @see org.eclipse.draw2d.Label#calculateTextSize()
1171
     */
1172
    protected Dimension calculateTextSize() {
1173
        /* getTextExtent calls GC#stringExtent and this adds
1174
         * a blank character before calculating the text size
1175
         * if the text's length is zero. However, we do want an 
1176
         * empty dimension if there is not text.
1177
         */
1178
        
1179
        if (getText().length() == 0) {
1180
            return EMPTY_DIMENSION.getCopy();
1181
        }
1182
        else {
1183
            Dimension textExtent = (Dimension) getFigureMapMode().DPtoLP(
1184
                FigureUtilities.getTextExtents(getText(), getFont()));
1185
            textExtent.height += getMapModeConstants().nDPtoLP_2 / 2;
1186
            return textExtent;
1187
        }
1188
    }    
1189
    
1190
    /*
1191
     * (non-Javadoc)
1192
     * 
1193
     * @see org.eclipse.draw2d.IFigure#getPreferredSize(int, int)
1194
     */
1195
    public Dimension getPreferredSize(int wHint, int hHint) {
1196
        if (prefSize == null || wHint != cached_PrefWidth
1197
            || hHint != cached_PrefHeight) {
1198
            
1199
            if (getText().length() == 0)
1200
                return prefSize = EMPTY_DIMENSION.getCopy();
1201
            
1202
            if (wHint < 0) {
1203
                prefSize = calculateLabelSize(getTextSize());
1204
                
1205
                Insets insets = getInsets();
1206
                prefSize.expand(insets.getWidth(), insets.getHeight());
1207
1208
            } else {
1209
                int _wHint = wHint;
1210
                switch (getTextPlacement()) {
1211
                    case EAST:
1212
                    case WEST:
1213
                        _wHint -= (getTotalIconSize().width + getIconTextGap());
1214
                        break;
1215
                }
1216
                
1217
                prefSize = calculateLabelSize(getLayoutManager().getPreferredSize(this, 
1218
                    _wHint, hHint));
1219
1220
                minSize = null;
1221
                prefSize.union(getMinimumSize(wHint, hHint));
1222
                cached_PrefWidth = wHint;
1223
                cached_PrefHeight = hHint;
1224
            }
1225
            
1226
            
1227
        }
1228
        
1229
        return prefSize;
1230
    }
1231
    
1232
    /**
1233
     * @see IFigure#getMinimumSize(int, int)
1234
     */
1235
    public Dimension getMinimumSize(int w, int h) {
1236
        if (minSize != null)
1237
            return minSize;
1238
        
1239
        if (getText().length() == 0)
1240
            return minSize = EMPTY_DIMENSION.getCopy();
1241
        
1242
        if (w < 0) {
1243
            minSize = calculateLabelSize(getTextSize());
1244
1245
            Insets insets = getInsets();
1246
            minSize.expand(insets.getWidth(), insets.getHeight());
1247
        } else {
1248
            minSize = new Dimension();
1249
1250
            int _wHint = w;
1251
            switch (getTextPlacement()) {
1252
                case EAST:
1253
                case WEST:
1254
                    _wHint -= (getTotalIconSize().width + getIconTextGap());
1255
                    break;
1256
            }
1257
            
1258
            if (getLayoutManager() != null)
1259
                minSize.setSize(getLayoutManager().getMinimumSize(this, _wHint, h));
1260
            
1261
            Dimension d = FlowUtilitiesEx.getEllipsisSize(textFlow.getFont(), getFigureMapMode()).
1262
                getIntersected(getTextSize());
1263
            
1264
            Dimension labelSize = calculateLabelSize(d);
1265
            
1266
            minSize.width = labelSize.width;
1267
            
1268
            Insets insets = getInsets();
1269
            minSize.expand(insets.getWidth(), insets.getHeight());
1270
        }
1271
        
1272
        return minSize;
1273
    }
1274
1275
    /*
1276
     * (non-Javadoc)
1277
     * 
1278
     * @see org.eclipse.draw2d.Figure#getMaximumSize()
1279
     */
1280
    public Dimension getMaximumSize() {
1281
        return prefSize;
1282
    }
1283
1284
    /**
1285
     * Get the maximum size, given width and height hints.<br>
1286
     * (Note: defaults to getMaximumSize())
1287
     * <p>
1288
     * 
1289
     * @param w
1290
     *            the width hint
1291
     * @param h
1292
     *            the height hint
1293
     * @return the maximum size.
1294
     */
1295
    public Dimension getMaximumSize(int w, int h) {
1296
        return getMaximumSize();
1297
    }
1298
1299
    /*
1300
     * (non-Javadoc)
1301
     * 
1302
     * @see org.eclipse.draw2d.Label#calculateSubStringTextSize()
1303
     */
1304
    protected Dimension calculateSubStringTextSize() {
1305
        return getSubStringTextSize();
1306
    }
1307
1308
    /**
1309
     * Returns the total number of icons being displayed.
1310
     * 
1311
     * @return int number of icons in the wrap label
1312
     */
1313
    protected int getNumberofIcons() {
1314
        if (iconInfo == null)
1315
            return 0;
1316
        return iconInfo.getNumberofIcons();
1317
    }
1318
1319
    /**
1320
     * getPlacement
1321
     * 
1322
     * @param flagOffset
1323
     * @return PositionConstant representing the placement
1324
     */
1325
    private int getPlacement(int flagOffset) {
1326
        int wrapValue = flags & (0x7 * flagOffset);
1327
        if (wrapValue == 0x1 * flagOffset)
1328
            return EAST;
1329
        else if (wrapValue == 0x2 * flagOffset)
1330
            return WEST;
1331
        else if (wrapValue == 0x3 * flagOffset)
1332
            return NORTH;
1333
        else if (wrapValue == 0x4 * flagOffset)
1334
            return SOUTH;
1335
1336
        return EAST;
1337
    }
1338
1339
    /* (non-Javadoc)
1340
     * @see org.eclipse.draw2d.Label#getText()
1341
     */
1342
    public String getText() {
1343
        return textFlow.getText();
1344
    }
1345
1346
    /**
1347
     * Returns the current alignment of the Label's text. The default text
1348
     * alignment is {@link PositionConstants#CENTER}.
1349
     * 
1350
     * @return the text alignment
1351
     */
1352
    public int getTextAlignment() {
1353
        return getAlignment(FLAG_TEXT_ALIGN);
1354
    }
1355
1356
    /**
1357
     * Returns the current placement of the label's text relative to its icon.
1358
     * The default text placement is {@link PositionConstants#EAST}.
1359
     * 
1360
     * @return the text placement
1361
     */
1362
    public int getTextPlacement() {
1363
        return getPlacement(FLAG_TEXT_PLACEMENT);
1364
    }
1365
1366
    /**
1367
     * Gets the horizontal alignment of the text (Left, Center or Right
1368
     * justified).
1369
     * <p>
1370
     * Default is {@link PositionConstants#CENTER}. Can be either
1371
     * {@link PositionConstants#CENTER}, {@link PositionConstants#LEFT}, or
1372
     * {@link PositionConstants#RIGHT}.
1373
     * 
1374
     * @return the horizontal alignment.
1375
     */
1376
    public int getTextHorizontalAlignment() {
1377
        return blockFlow.getHorizontalAligment();
1378
    }
1379
1380
    /**
1381
     * getTotalIconSize Calculates the total union of icon sizes
1382
     * 
1383
     * @return Dimension that is the union of icon sizes
1384
     */
1385
    protected Dimension getTotalIconSize() {
1386
        if (iconInfo == null)
1387
            return EMPTY_DIMENSION;
1388
        return iconInfo.getTotalIconSize(getFigureMapMode());
1389
    }
1390
1391
    /**
1392
     * Returns true if the WrapLabel has focus, false otherwise.
1393
     * 
1394
     * @return the focus state of this label
1395
     */
1396
    public boolean hasFocus() {
1397
        return textFlow.hasFocus();
1398
    }
1399
1400
    /**
1401
     * Returns true if the WrapLabel has icons, false otherwise.
1402
     * 
1403
     * @return
1404
     */
1405
    protected boolean hasIcons() {
1406
        return (getNumberofIcons() > 0);
1407
    }
1408
1409
    /*
1410
     * (non-Javadoc)
1411
     * 
1412
     * @see org.eclipse.draw2d.Label#invalidate()
1413
     */
1414
    public void invalidate() {
1415
        clearLocations();
1416
        if (iconInfo != null)
1417
            iconInfo.invalidate();
1418
        super.invalidate();
1419
    }
1420
    
1421
    public void validate() {
1422
        super.validate();
1423
        // calculateLocations();
1424
    }
1425
1426
    /**
1427
     * @return the selection state of this label
1428
     */
1429
    public boolean isSelected() {
1430
        return textFlow.isSelected();
1431
    }
1432
1433
    /**
1434
     * @return whether the label text is striked-through
1435
     */
1436
    public boolean isTextStrikedThrough() {
1437
        return textFlow.isTextStrikeThrough();
1438
    }
1439
1440
    /**
1441
     * Returns <code>true</code> if the label's text is currently truncated,
1442
     * <code>false</code> otherwise.
1443
     * 
1444
     * @return <code>true</code> if the label's text is truncated
1445
     */
1446
    public boolean isTextTruncated() {
1447
        return (getBounds().width < textFlow.getBounds().width)
1448
            || (getBounds().height < textFlow.getBounds().height);
1449
    }
1450
1451
    /**
1452
     * @return whether the label text is underlined
1453
     */
1454
    public boolean isTextUnderlined() {
1455
        return textFlow.isTextUnderlined();
1456
    }
1457
1458
    /**
1459
     * @return wether the label text wrap is on
1460
     */
1461
    public boolean isTextWrapped() {
1462
        return (flags & FLAG_WRAP) != 0;
1463
    }
1464
1465
    /**
1466
     * Determines the insertion points based on the Label Alignment.<br>
1467
     * Note: should be called by calculateLocations only.
1468
     * <p>
1469
     * 
1470
     * @param textPlacement
1471
     *            the placement of the text relative to the icon.
1472
     * @param totalIconSize
1473
     *            the total size of all icons.
1474
     * @param totalPossibleSize
1475
     *            the total possible size of icon(s) and text.
1476
     */
1477
    private void layoutTextAndIcons(int textPlacement, Dimension totalIconSize,
1478
            Dimension totalPossibleSize) {
1479
        switch (getLabelAlignment()) {
1480
            case TOP:
1481
                calculateTextPlacement(new Point(0, 0), textPlacement,
1482
                    totalIconSize);
1483
                break;
1484
            case BOTTOM:
1485
                calculateTextPlacement(new Point(0, getBounds().height
1486
                    - totalPossibleSize.height), textPlacement, totalIconSize);
1487
                break;
1488
            case CENTER: // RIGHT and LEFT not supported.
1489
            case RIGHT:
1490
            case LEFT:
1491
                calculateTextPlacement(new Point(0,
1492
                    (getBounds().height - totalPossibleSize.height) / 2),
1493
                    textPlacement, totalIconSize);
1494
                break;
1495
                
1496
            /* the following commented lines of code obtain original functionality
1497
             * of label alignment but the code is buggy. To use, remove CENTER and RIGHT
1498
             * cases above and uncomment the lines below.
1499
             */
1500
//            case RIGHT:
1501
//                calculateTextBounds(false);
1502
//                if (literalTextBounds == null || literalTextBounds.width == 0) {
1503
//                    calculateTextPlacement(new Point(
1504
//                        getBounds().width - totalPossibleSize.width,
1505
//                        (getBounds().height - totalPossibleSize.height) / 2),
1506
//                        textPlacement, totalIconSize);
1507
//                } else {
1508
//                    Dimension _totalLabelSize = calculateLabelSize(literalTextBounds
1509
//                        .getSize());
1510
//                    calculateTextPlacement(new Point(
1511
//                        (getBounds().width - _totalLabelSize.width),
1512
//                        (getBounds().height - _totalLabelSize.height) / 2),
1513
//                        textPlacement, totalIconSize);
1514
//                }
1515
//                break;
1516
//            default: // CENTER
1517
//                calculateTextBounds(false);
1518
//                if (literalTextBounds == null || literalTextBounds.width == 0) {
1519
//                    calculateTextPlacement(new Point(
1520
//                        (getBounds().width - totalPossibleSize.width) / 2,
1521
//                        (getBounds().height - totalPossibleSize.height) / 2),
1522
//                        textPlacement, totalIconSize);
1523
//                } else {
1524
//                    Dimension _totalLabelSize = calculateLabelSize(literalTextBounds
1525
//                        .getSize());
1526
//                    calculateTextPlacement(new Point(
1527
//                        (getBounds().width - _totalLabelSize.width) / 2,
1528
//                        (getBounds().height - _totalLabelSize.height) / 2),
1529
//                        textPlacement, totalIconSize);
1530
//                }
1531
        }
1532
    }
1533
1534
    /**
1535
     * @see Figure#paintFigure(Graphics)
1536
     */
1537
    protected void paintFigure(Graphics graphics) {
1538
        if (isOpaque())
1539
            super.paintFigure(graphics);
1540
1541
        if ((flags & FLAG_CALCULATE_LOCATIONS) != 0) {
1542
            calculateLocations();
1543
            setFlag(FLAG_CALCULATE_LOCATIONS, false);
1544
        }
1545
        
1546
        if (hasIcons())
1547
            paintIcons(graphics);
1548
    }
1549
1550
    /**
1551
     * Paints the icon(s)
1552
     * 
1553
     * @param graphics
1554
     *            The graphics context
1555
     */
1556
    private void paintIcons(Graphics graphics) {
1557
        Rectangle figBounds = getBounds();
1558
        graphics.translate(figBounds.x, figBounds.y);
1559
1560
        Point p = Point.SINGLETON;
1561
        p.setLocation(getIconLocation());
1562
1563
        int num = getNumberofIcons();
1564
        for (int i = 0; i < num; i++) {
1565
            Image tempIcon = getIcon(i);
1566
            if (tempIcon != null) {
1567
                graphics.drawImage(tempIcon, p);
1568
                p.x += getIconSize(i).width;
1569
            }
1570
        }
1571
1572
        graphics.translate(-figBounds.x, -figBounds.y);
1573
    }
1574
1575
    /**
1576
     * setAlignmentFlags
1577
     * 
1578
     * @param align
1579
     * @param flagOffset
1580
     */
1581
    private void setAlignmentFlags(int align, int flagOffset) {
1582
        flags &= ~(0x7 * flagOffset);
1583
        switch (align) {
1584
            case CENTER:
1585
                flags |= 0x1 * flagOffset;
1586
                break;
1587
            case TOP:
1588
                flags |= 0x2 * flagOffset;
1589
                break;
1590
            case LEFT:
1591
                flags |= 0x3 * flagOffset;
1592
                break;
1593
            case RIGHT:
1594
                flags |= 0x4 * flagOffset;
1595
                break;
1596
            case BOTTOM:
1597
                flags |= 0x5 * flagOffset;
1598
                break;
1599
        }
1600
    }
1601
1602
    /**
1603
     * Sets the focus state of this label
1604
     * 
1605
     * @param b
1606
     *            true will cause a focus rectangle to be drawn around the text
1607
     *            of the Label
1608
     */
1609
    public void setFocus(boolean b) {
1610
        textFlow.setFocus(b);
1611
    }
1612
1613
    /*
1614
     * (non-Javadoc)
1615
     * 
1616
     * @see org.eclipse.draw2d.Figure#setFont(org.eclipse.swt.graphics.Font)
1617
     */
1618
    public void setFont(Font f) {
1619
        super.setFont(f);
1620
        
1621
        /*
1622
         * Simply calling revalidate 
1623
         * may not make textFlow an invalid figure and revalidate it. 
1624
         * Therefore, when font is changed (e.g. size change, bold, italic),
1625
         * the layout manager for textFlow may not get invoked.
1626
         */
1627
        textFlow.revalidate();
1628
    }
1629
1630
    /**
1631
     * Sets the label's icon to the passed image.
1632
     * 
1633
     * @param image
1634
     *            the new label image
1635
     */
1636
    public void setIcon(Image image) {
1637
        setIcon(image, 0);
1638
    }
1639
1640
    /**
1641
     * Sets the label's icon at given index
1642
     * 
1643
     * @param image The icon image or null to remove the icon
1644
     * @param index The icon index
1645
     */
1646
    public void setIcon(Image image, int index) {
1647
        if (iconInfo == null) {
1648
            if (index == 0) {
1649
                iconInfo = getMapModeConstants().getSingleIconInfo(image);
1650
            } else {
1651
                iconInfo = new MultiIconInfo();
1652
                iconInfo.setIcon(image, index);
1653
            }
1654
            revalidate();
1655
            repaint();// Call repaint, in case the image dimensions are the same.           
1656
        } else if (iconInfo.getIcon(index) != image) {
1657
            if (iconInfo.getMaxIcons() == 1) {
1658
                if (index == 0) {
1659
                    iconInfo = getMapModeConstants().getSingleIconInfo(image);
1660
                    revalidate();
1661
                    repaint();// Call repaint, in case the image dimensions are the same.
1662
                    return;
1663
                }
1664
                IconInfo oldIconInfo = iconInfo;
1665
                iconInfo = new MultiIconInfo();
1666
                iconInfo.setIcon(oldIconInfo.getIcon(0), 0);
1667
            }
1668
            iconInfo.setIcon(image, index);
1669
            revalidate();
1670
            repaint();// Call repaint, in case the image dimensions are the same.
1671
        }   
1672
    }
1673
1674
    /**
1675
     * Sets the icon alignment relative to the text. The default is
1676
     * {@link PositionConstants#CENTER}.
1677
     * <p>
1678
     * 
1679
     * If the text placement is NORTH/SOUTH, icon alignment should be either
1680
     * LEFT, CENTER, or RIGHT to the icon.
1681
     * <p>
1682
     * 
1683
     * If the text placement is EAST/WEST, icon alignment should be either TOP,
1684
     * CENTER, or BOTTOM to the icon.
1685
     * <p>
1686
     * 
1687
     * @param align
1688
     *            the icon alignment
1689
     */
1690
    public void setIconAlignment(int align) {
1691
        if (getIconAlignment() == align)
1692
            return;
1693
        setAlignmentFlags(align, FLAG_ICON_ALIGN);
1694
        revalidate();
1695
        repaint();
1696
    }
1697
1698
    /**
1699
     * Sets the Label's alignment to the passed value. The default is
1700
     * {@link PositionConstants#TOP}. Other possible values are
1701
     * {@link PositionConstants#CENTER},{@link PositionConstants#BOTTOM}.
1702
     * <p>
1703
     * Note:{@link PositionConstants#LEFT} and {@link PositionConstants#RIGHT}
1704
     * are not supported as wrap label occupies the entire client area. Use
1705
     * horizontal alignment instead (see WrapLabel#setHorizontalAlignment(int)).
1706
     * <p>
1707
     * 
1708
     * @param align
1709
     *            label alignment
1710
     */
1711
    public void setLabelAlignment(int align) {
1712
        if (getLabelAlignment() == align)
1713
            return;
1714
        
1715
        setAlignmentFlags(align, FLAG_LABEL_ALIGN);
1716
        
1717
        revalidate();
1718
        repaint();
1719
    }
1720
    
1721
    /**
1722
     * setPlacementFlags
1723
     * 
1724
     * @param align
1725
     * @param flagOffset
1726
     */
1727
    private void setPlacementFlags(int align, int flagOffset) {
1728
        flags &= ~(0x7 * flagOffset);
1729
        switch (align) {
1730
            case EAST:
1731
                flags |= 0x1 * flagOffset;
1732
                break;
1733
            case WEST:
1734
                flags |= 0x2 * flagOffset;
1735
                break;
1736
            case NORTH:
1737
                flags |= 0x3 * flagOffset;
1738
                break;
1739
            case SOUTH:
1740
                flags |= 0x4 * flagOffset;
1741
                break;
1742
        }
1743
    }
1744
1745
    /**
1746
     * Sets the selection state of this label
1747
     * 
1748
     * @param b
1749
     *            true will cause the label to appear selected
1750
     */
1751
    public void setSelected(boolean b) {
1752
        textFlow.setSelected(b);
1753
    }
1754
1755
    /*
1756
     * (non-Javadoc)
1757
     * 
1758
     * @see org.eclipse.draw2d.Label#setText(java.lang.String)
1759
     */
1760
    public void setText(String s) {
1761
        if (s == null)
1762
            s = "";//$NON-NLS-1$
1763
        if (getText().equals(s))
1764
            return;
1765
        textFlow.setText(s);
1766
        revalidate();
1767
        repaint();
1768
    }
1769
1770
    /**
1771
     * Sets the text alignment of the text relative to the icon. The default is
1772
     * {@link PositionConstants#CENTER}.
1773
     * <p>
1774
     * 
1775
     * If the text placement is NORTH/SOUTH, text alignment should be either
1776
     * LEFT, CENTER, or RIGHT to the icon.
1777
     * <p>
1778
     * 
1779
     * If the text placement is EAST/WEST, text alignment should be either TOP,
1780
     * CENTER, or BOTTOM to the icon.
1781
     * 
1782
     * @param align
1783
     *            the text alignment
1784
     */
1785
    public void setTextAlignment(int align) {
1786
        if (getTextAlignment() == align)
1787
            return;
1788
        setAlignmentFlags(align, FLAG_TEXT_ALIGN);
1789
        revalidate();
1790
        repaint();
1791
    }
1792
1793
    /**
1794
     * Sets the text placement of the label relative to its icon. The default is
1795
     * {@link PositionConstants#EAST}. Other possible values are
1796
     * {@link PositionConstants#NORTH},{@link PositionConstants#SOUTH}and
1797
     * {@link PositionConstants#WEST}. <p>
1798
     * 
1799
     * e.g. PositionConstants#EAST indicates the text is on the east of the icon. <p>
1800
     * 
1801
     * @param where
1802
     *            the text placement
1803
     */
1804
    public void setTextPlacement(int where) {
1805
        if (getTextPlacement() == where)
1806
            return;
1807
        setPlacementFlags(where, FLAG_TEXT_PLACEMENT);
1808
        revalidate();
1809
        repaint();
1810
    }
1811
1812
    /**
1813
     * Sets whether the label text should be striked-through
1814
     * 
1815
     * @param b
1816
     *            Whether the label text should be striked-through
1817
     */
1818
    public void setTextStrikeThrough(boolean b) {
1819
        textFlow.setStrikeThrough(b);
1820
    }
1821
1822
    /**
1823
     * Sets whether the label text should be underlined
1824
     * 
1825
     * @param b 
1826
     *          Whether the label text should be underlined
1827
     */
1828
    public void setTextUnderline(boolean b) {
1829
        textFlow.setTextUnderline(b);
1830
    }
1831
    
1832
    /**
1833
     * Sets up the flow page, block flow and text flow figures 
1834
     * with appropriate wrapping layouts. <p>
1835
     * 
1836
     * @param enableWrapping set to true if wrapping should be enabled.
1837
     */
1838
    private void setupTextFigures(boolean enableWrapping) {
1839
        
1840
        String oldText = ""; //$NON-NLS-1$
1841
        
1842
        int oldTextHorizontalAlignment = CENTER;
1843
        
1844
        if (textFlow != null) {
1845
            oldText = textFlow.getText();
1846
            oldTextHorizontalAlignment = blockFlow.getHorizontalAligment();
1847
            remove(flowPage);
1848
        }
1849
        
1850
        flowPage = new FlowPage();
1851
        blockFlow = new BlockFlow();
1852
        textFlow = new SelectableTextFlow();
1853
        
1854
        flowPage.setLayoutManager(new PageFlowLayout(flowPage));
1855
        blockFlow.setLayoutManager(new BlockFlowLayout(blockFlow));
1856
1857
        if (enableWrapping) {
1858
            textFlow.setLayoutManager(new ParagraphTextLayoutEx(textFlow,
1859
                ParagraphTextLayoutEx.WORD_WRAP_SOFT));
1860
        } else {
1861
            textFlow.setLayoutManager(new SingleLineTextLayoutWithEllipses(textFlow));
1862
        }
1863
1864
        blockFlow.add(textFlow);
1865
        flowPage.add(blockFlow);
1866
        add(flowPage);
1867
        
1868
        textFlow.setText(oldText);
1869
        setTextHorizontalAlignment(oldTextHorizontalAlignment);
1870
    }
1871
1872
    /**
1873
     * Sets whether the label text should wrap
1874
     * 
1875
     * @param b
1876
     *            whether the label text should wrap
1877
     */
1878
    public void setTextWrap(boolean b) {
1879
        if (isTextWrapped() == b)
1880
            return;
1881
        setFlag(FLAG_WRAP, b);
1882
        
1883
        setupTextFigures(b);
1884
        
1885
        revalidate();
1886
        repaint();
1887
    }
1888
1889
    /**
1890
     * Sets the horizontal alignment of the label text. The default is
1891
     * {@link PositionConstants#CENTER}.
1892
     * <p>
1893
     * 
1894
     * It can be either {@link PositionConstants#LEFT},
1895
     * {@link PositionConstants#CENTER}, or {@link PositionConstants#RIGHT}
1896
     * <p>
1897
     * 
1898
     * @param i
1899
     *            the horizontal alignment.
1900
     */
1901
    public void setTextHorizontalAlignment(int i) {
1902
        blockFlow.setHorizontalAligment(i);
1903
1904
        revalidate();
1905
        repaint();
1906
    }
1907
    
1908
    /**
1909
     * Obtains the text offset the caret should be placed given
1910
     * the location in absolute coordinates. 
1911
     * @param location where the caret should be placed in 
1912
     *        absolute pixel coordinates
1913
     * @return text offset
1914
     */
1915
    public int getCaretOffset(Point location) {
1916
        Rectangle _textBounds = getTextBounds().getCopy();
1917
        translateToAbsolute(_textBounds);
1918
        
1919
        if (!_textBounds.contains(location))
1920
            return -1;
1921
1922
        return textFlow.getCaretOffset(location);
1923
    }
1924
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SingleLineTextLayoutWithEllipses.java (+160 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.internal.text;
13
14
import java.util.List;
15
16
import org.eclipse.draw2d.text.TextFlow;
17
import org.eclipse.draw2d.text.TextFragmentBox;
18
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
19
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
20
import org.eclipse.swt.graphics.Font;
21
22
/**
23
 * A text layout that does not wrap words but inserts ellipses to
24
 * truncated text.
25
 * @author satif
26
 *
27
 */
28
public class SingleLineTextLayoutWithEllipses
29
    extends org.eclipse.draw2d.text.TextLayout
30
    implements TextLayoutManagerWithMapMode {
31
32
    private static final String[] DELIMITERS = {"\r\n", //$NON-NLS-1$
33
        "\n", //$NON-NLS-1$
34
        "\r"};//$NON-NLS-1$
35
36
    private static int result;
37
38
    private static int delimeterLength;
39
40
    private IMapMode mm = MapModeUtil.getMapMode();
41
42
    /* (non-Javadoc)
43
     * @see org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode#setMapMode(org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode)
44
     */
45
    public void setMapMode(IMapMode mm) {
46
        this.mm = mm;
47
    }
48
49
    /**
50
     * Creates a new SimpleTextLayout with the given TextFlow
51
     * 
52
     * @param flow
53
     *            the TextFlow
54
     */
55
    public SingleLineTextLayoutWithEllipses(TextFlow flow) {
56
        super(flow);
57
    }
58
59
    private int nextLineBreak(String text, int offset) {
60
        result = text.length();
61
        delimeterLength = 0;
62
        int current;
63
        for (int i = 0; i < DELIMITERS.length; i++) {
64
            current = text.indexOf(DELIMITERS[i], offset);
65
            if (current != -1 && current < result) {
66
                result = current;
67
                delimeterLength = DELIMITERS[i].length();
68
            }
69
        }
70
        return result;
71
    }
72
73
    /**
74
     * @see org.eclipse.draw2d.text.FlowFigureLayout#layout()
75
     */
76
    protected void layout() {
77
        TextFlow textFlow = (TextFlow) getFlowFigure();
78
        String text = textFlow.getText();
79
        List fragments = textFlow.getFragments();
80
        Font font = textFlow.getFont();
81
        TextFragmentBox fragment;
82
        int i = 0;
83
        int offset = 0;
84
        int ellipsesSize = FlowUtilitiesEx.getEllipsisSize(font, mm).width;
85
86
        do {
87
            nextLineBreak(text, offset);
88
            fragment = getFragment(i++, fragments);
89
            fragment.length = result - offset;
90
            fragment.offset = offset;
91
            fragment.setWidth(-1);
92
            fragment.setTruncated(false);
93
            FlowUtilitiesEx.setupFragment(fragment, font, text, mm);
94
95
            int remainingLineWidth = getContext().getRemainingLineWidth();
96
97
            // if the text is truncated...
98
99
            if (remainingLineWidth > 0
100
                && remainingLineWidth < fragment.getWidth()) {
101
102
                // estimate the length of the string just before we add the
103
                // ellipses...
104
105
                remainingLineWidth -= ellipsesSize;
106
                
107
                if (remainingLineWidth <= 0)
108
                    break;
109
110
                fragment.setTruncated(true);
111
112
                // we estimate the length of the fragment by using average
113
                // character
114
                // width then +/- as required. This drastically increases
115
                // the speed.
116
117
                int min = (int) (remainingLineWidth / FlowUtilitiesEx.getAverageCharWidth(
118
                    fragment, font, mm));
119
120
                fragment.length = min;
121
                fragment.setWidth(-1);
122
                FlowUtilitiesEx.setupFragment(fragment, font, text, mm);
123
                int dimensionX = fragment.getWidth();
124
125
                int maxLength = text.length() - offset;
126
                while (dimensionX < remainingLineWidth) {
127
                    min++;
128
                    if (min > maxLength)
129
                        break;
130
131
                    fragment.length = min;
132
                    fragment.setWidth(-1);
133
                    FlowUtilitiesEx.setupFragment(fragment, font, text,
134
                        mm);
135
                    dimensionX = fragment.getWidth();
136
                }
137
                
138
                while (dimensionX > remainingLineWidth) {
139
                    min--;
140
                    if (min <= 0)
141
                        break;
142
143
                    fragment.length = min;
144
                    fragment.setWidth(-1);
145
                    FlowUtilitiesEx.setupFragment(fragment, font, text,
146
                        mm);
147
                    dimensionX = fragment.getWidth();
148
                }
149
            } else
150
                fragment.setTruncated(false);
151
152
            getContext().addToCurrentLine(fragment);
153
            getContext().endLine();
154
            offset = result + delimeterLength;
155
        } while (offset < text.length());
156
        // Remove the remaining unused fragments.
157
        while (i < fragments.size())
158
            fragments.remove(i++);
159
    }
160
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/TextLayoutManagerWithMapMode.java (+36 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.internal.text;
13
14
import org.eclipse.draw2d.LayoutManager;
15
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
16
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
17
18
/**
19
 * Interface for text layout managers to use map mode.
20
 * @author satif
21
 *
22
 */
23
public interface TextLayoutManagerWithMapMode
24
    extends LayoutManager {
25
26
    /** Set the map mode to use within this text layout 
27
     * manager. <p> 
28
     * 
29
     * Default map mode should be the one given by
30
     * MapModeUtil.getMapMode() but may also depend on
31
     * the specific implementation of the text layout manager.
32
     * 
33
     * @param mm the map mode to set.
34
     */
35
    public void setMapMode(IMapMode mm);
36
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/FlowUtilitiesEx.java (+360 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.internal.text;
13
14
15
import org.eclipse.draw2d.FigureUtilities;
16
import org.eclipse.draw2d.geometry.Dimension;
17
import org.eclipse.draw2d.text.FlowContext;
18
import org.eclipse.draw2d.text.FlowUtilities;
19
import org.eclipse.draw2d.text.ParagraphTextLayout;
20
import org.eclipse.draw2d.text.TextFragmentBox;
21
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
22
import org.eclipse.swt.graphics.Font;
23
import org.eclipse.swt.graphics.TextLayout;
24
25
import com.ibm.icu.text.BreakIterator;
26
27
/**
28
 * This class is a direct copy from Draw2D's FlowUtilities but with map mode
29
 * functionality.
30
 * @author satif
31
 */
32
public class FlowUtilitiesEx
33
    extends FlowUtilities {
34
35
    public static final String ELLIPSIS = "..."; //$NON-NLS-1$
36
37
    private static final BreakIterator INTERNAL_LINE_BREAK = BreakIterator
38
        .getLineInstance();
39
40
41
    /**
42
     * Get the ellipses text size given the font and the map mode.
43
     * @param font the font to measure the ellipses size.
44
     * @param mm the map mode the fragment will be in.
45
     * @return the width of the ellipses
46
     */
47
    public static Dimension getEllipsisSize(Font font, IMapMode mm) {
48
        return (Dimension)mm
49
            .DPtoLP(FigureUtilities.getStringExtents(ELLIPSIS, font));
50
    }
51
52
    /**
53
     * Gets the average character width.
54
     * 
55
     * @param fragment the supplied TextFragmentBox to use for calculation.
56
     *                 if the length is 0 or if the width is or below 0,
57
     *                 the average character width is taken from standard 
58
     *                 font metrics.
59
     * @param font     the font to use incase the TextFragmentBox conditions 
60
     *                 above are true.
61
     * @param mm       the map mode the fragment will be in.
62
     * @return         the average character width
63
     */
64
    public static float getAverageCharWidth(TextFragmentBox fragment,
65
            Font font, IMapMode mm) {
66
        if (fragment.getWidth() > 0 && fragment.length != 0)
67
            return fragment.getWidth() / (float) fragment.length;
68
        return mm.DPtoLP(getFontMetrics(font).getAverageCharWidth());
69
    }
70
71
    /**
72
     * Measures the width of the supplied (sub)string. <p>
73
     * 
74
     * @param frag the supplied TextFragmentBox (needed to check
75
     *             if it uses BiDi).
76
     * @param string the String to measure.
77
     * @param guess the length of the supplied String to measure.
78
     * @param font the font the fragment will use.
79
     * @param mm the map mode the fragment will be in.
80
     * @return the width of the supplied String
81
     */
82
    public static int measureString(TextFragmentBox frag, String string,
83
            int guess, Font font, IMapMode mm) {
84
        if (frag.requiresBidi()) {
85
            // The text and/or could have changed if the lookAhead was invoked.
86
            // This will
87
            // happen at most once.
88
            TextLayout _layout = getTextLayout();
89
            _layout.setText(string);
90
            _layout.setFont(font);
91
            return mm.DPtoLP(_layout.getBounds(0, guess - 1).width);
92
        } else
93
            return mm.DPtoLP(getStringDimension(string.substring(0, guess),
94
                font).x);
95
    }
96
97
    /**
98
     * Sets up the fragment's width according to BiDi and truncation 
99
     * given all the parameters and the fields in the supplied fragment.<p>
100
     * 
101
     * @param frag the supplied TextFragmentBox
102
     * @param f the font the fragment will use.
103
     * @param s the string the fragment will use.
104
     * @param mm the map mode the fragment will be in.
105
     */
106
    public static void setupFragment(TextFragmentBox frag, Font f, String s,
107
            IMapMode mm) {
108
        if (frag.getWidth() == -1 || frag.isTruncated()) {
109
            int width;
110
            if (s.length() == 0 || frag.length == 0)
111
                width = 0;
112
            else if (frag.requiresBidi()) {
113
                TextLayout textLayout = getTextLayout();
114
                textLayout.setFont(f);
115
                textLayout.setText(s);
116
                width = mm
117
                    .DPtoLP(textLayout.getBounds(0, frag.length - 1).width);
118
            } else
119
                width = mm.DPtoLP(getStringDimension(s
120
                    .substring(0, frag.length), f).x);
121
            if (frag.isTruncated())
122
                width += getEllipsisSize(f, mm).width;
123
            frag.setWidth(width);
124
        }
125
    }
126
127
    /**
128
     * Sets up a fragment and returns the number of characters consumed from the
129
     * given String. An average character width can be provided as a hint for
130
     * faster calculation. If a fragment's bidi level is set, a TextLayout will
131
     * be used to calculate the width.
132
     * 
133
     * @param frag
134
     *            the TextFragmentBox
135
     * @param string
136
     *            the String
137
     * @param font
138
     *            the Font used for measuring
139
     * @param context
140
     *            the flow context
141
     * @param wrapping
142
     *            the word wrap style
143
     * @return the number of characters that will fit in the given space; can be
144
     *         0 (eg., when the first character of the given string is a
145
     *         newline)
146
     */
147
    public static int wrapFragmentInContext(TextFragmentBox frag,
148
            String string, FlowContext context, LookAhead lookahead, Font font,
149
            int wrapping, IMapMode mm) {
150
        frag.setTruncated(false);
151
        int strLen = string.length();
152
        if (strLen == 0) {
153
            frag.setWidth(-1);
154
            frag.length = 0;
155
            setupFragment(frag, font, string, mm);
156
            context.addToCurrentLine(frag);
157
            return 0;
158
        }
159
160
        INTERNAL_LINE_BREAK.setText(string);
161
162
        initBidi(frag, string, font);
163
        float avgCharWidth = getAverageCharWidth(frag, font, mm);
164
        frag.setWidth(-1);
165
166
        /*
167
         * Setup initial boundaries within the string.
168
         */
169
        int absoluteMin = 0;
170
        int max, min = 1;
171
        if (wrapping == ParagraphTextLayout.WORD_WRAP_HARD) {
172
            absoluteMin = INTERNAL_LINE_BREAK.next();
173
            while (absoluteMin > 0
174
                && Character.isWhitespace(string.charAt(absoluteMin - 1)))
175
                absoluteMin--;
176
            min = Math.max(absoluteMin, 1);
177
        }
178
        int firstDelimiter = findFirstDelimeter(string);
179
        if (firstDelimiter == 0)
180
            min = max = 0;
181
        else
182
            max = Math.min(strLen, firstDelimiter) + 1;
183
184
        int availableWidth = context.getRemainingLineWidth();
185
        int guess = 0, guessSize = 0;
186
187
        while (true) {
188
            if ((max - min) <= 1) {
189
                if (min == absoluteMin
190
                    && context.isCurrentLineOccupied()
191
                    && !context.getContinueOnSameLine()
192
                    && availableWidth < measureString(frag, string, min, font,
193
                        mm)
194
                        + ((min == strLen && lookahead != null) ? lookahead
195
                            .getWidth()
196
                            : 0)) {
197
                    context.endLine();
198
                    availableWidth = context.getRemainingLineWidth();
199
                    max = Math.min(strLen, firstDelimiter) + 1;
200
                    if ((max - min) <= 1)
201
                        break;
202
                } else
203
                    break;
204
            }
205
            // Pick a new guess size
206
            // New guess is the last guess plus the missing width in pixels
207
            // divided by the average character size in pixels
208
            guess += 0.5f + (availableWidth - guessSize) / avgCharWidth;
209
210
            if (guess >= max)
211
                guess = max - 1;
212
            if (guess <= min)
213
                guess = min + 1;
214
215
            guessSize = measureString(frag, string, guess, font, mm);
216
217
            if (guess == strLen && lookahead != null
218
                && !canBreakAfter(string.charAt(strLen - 1))
219
                && guessSize + lookahead.getWidth() > availableWidth) {
220
                max = guess;
221
                continue;
222
            }
223
224
            if (guessSize <= availableWidth) {
225
                min = guess;
226
                frag.setWidth(guessSize);
227
                if (guessSize == availableWidth)
228
                    max = guess + 1;
229
            } else
230
                max = guess;
231
        }
232
233
        int result = min;
234
        boolean continueOnLine = false;
235
        if (min == strLen) {
236
            // Everything fits
237
            if (string.charAt(strLen - 1) == ' ') {
238
                if (frag.getWidth() == -1) {
239
                    frag.length = result;
240
                    frag
241
                        .setWidth(measureString(frag, string, result, font, mm));
242
                }
243
                if (lookahead.getWidth() > availableWidth - frag.getWidth()) {
244
                    frag.length = result - 1;
245
                    frag.setWidth(-1);
246
                } else
247
                    frag.length = result;
248
            } else {
249
                continueOnLine = !canBreakAfter(string.charAt(strLen - 1));
250
                frag.length = result;
251
            }
252
        } else if (min == firstDelimiter) {
253
            // move result past the delimiter
254
            frag.length = result;
255
            if (string.charAt(min) == '\r') {
256
                result++;
257
                if (++min < strLen && string.charAt(min) == '\n')
258
                    result++;
259
            } else if (string.charAt(min) == '\n')
260
                result++;
261
        } else if (string.charAt(min) == ' '
262
            || canBreakAfter(string.charAt(min - 1))
263
            || INTERNAL_LINE_BREAK.isBoundary(min)) {
264
            frag.length = min;
265
            if (string.charAt(min) == ' ')
266
                result++;
267
            else if (string.charAt(min - 1) == ' ') {
268
                frag.length--;
269
                frag.setWidth(-1);
270
            }
271
        } else
272
            out: {
273
                // In the middle of an unbreakable offset
274
                result = INTERNAL_LINE_BREAK.preceding(min);
275
                if (result == 0) {
276
                    switch (wrapping) {
277
                        case ParagraphTextLayout.WORD_WRAP_TRUNCATE:
278
                            int truncatedWidth = availableWidth - getEllipsisSize(font, mm).width;
279
                            if (truncatedWidth > 0) {
280
                                // $TODO this is very slow. It should be using
281
                                // avgCharWidth to go faster
282
                                while (min > 0) {
283
                                    guessSize = measureString(frag, string,
284
                                        min, font, mm);
285
                                    if (guessSize <= truncatedWidth)
286
                                        break;
287
                                    min--;
288
                                }
289
                                frag.length = min;
290
                            } else
291
                                frag.length = 0;
292
                            frag.setTruncated(true);
293
                            result = INTERNAL_LINE_BREAK.following(max - 1);
294
                            break out;
295
296
                        default:
297
                            result = min;
298
                            break;
299
                    }
300
                }
301
                frag.length = result;
302
                if (string.charAt(result - 1) == ' ')
303
                    frag.length--;
304
                frag.setWidth(-1);
305
            }
306
307
        setupFragment(frag, font, string, mm);
308
        context.addToCurrentLine(frag);
309
        context.setContinueOnSameLine(continueOnLine);
310
        return result;
311
    }
312
313
    public static void setupEllipses(TextFragmentBox fragment, String text, 
314
            int remainingLineWidth, Font font, IMapMode mm) {
315
        remainingLineWidth -= getEllipsisSize(font, mm).width;
316
        
317
        if (remainingLineWidth <= 0)
318
            return;
319
320
        fragment.setTruncated(true);
321
322
        // we estimate the length of the fragment by using average
323
        // character
324
        // width then +/- as required. This drastically increases
325
        // the speed.
326
327
        int min = (int) (remainingLineWidth / FlowUtilitiesEx.getAverageCharWidth(
328
            fragment, font, mm));
329
330
        fragment.length = min;
331
        fragment.setWidth(-1);
332
        FlowUtilitiesEx.setupFragment(fragment, font, text, mm);
333
        int dimensionX = fragment.getWidth();
334
335
        int maxLength = text.length() - fragment.offset;
336
        while (dimensionX < remainingLineWidth) {
337
            min++;
338
            if (min > maxLength)
339
                break;
340
341
            fragment.length = min;
342
            fragment.setWidth(-1);
343
            FlowUtilitiesEx.setupFragment(fragment, font, text,
344
                mm);
345
            dimensionX = fragment.getWidth();
346
        }
347
        
348
        while (dimensionX > remainingLineWidth) {
349
            min--;
350
            if (min <= 0)
351
                break;
352
353
            fragment.length = min;
354
            fragment.setWidth(-1);
355
            FlowUtilitiesEx.setupFragment(fragment, font, text,
356
                mm);
357
            dimensionX = fragment.getWidth();
358
        }
359
    }
360
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/ParagraphTextLayoutEx.java (+240 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     IBM Corporation - initial API and implementation
10
 *******************************************************************************/
11
package org.eclipse.gmf.runtime.draw2d.ui.internal.text;
12
13
import java.util.List;
14
15
import org.eclipse.draw2d.text.FlowBorder;
16
import org.eclipse.draw2d.text.FlowContext;
17
import org.eclipse.draw2d.text.FlowFigure;
18
import org.eclipse.draw2d.text.ParagraphTextLayout;
19
import org.eclipse.draw2d.text.TextFlow;
20
import org.eclipse.draw2d.text.TextFragmentBox;
21
import org.eclipse.draw2d.text.FlowUtilities.LookAhead;
22
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
23
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
24
import org.eclipse.swt.graphics.Font;
25
26
/**
27
 * An extended Paragraph Text layout that considers map mode.
28
 * @author satif
29
 *
30
 */
31
public class ParagraphTextLayoutEx
32
    extends ParagraphTextLayout
33
    implements TextLayoutManagerWithMapMode {
34
35
    private IMapMode mm = MapModeUtil.getMapMode();
36
    
37
    
38
    /**
39
     * Note: This method is a direct copy from 
40
     * org.eclipse.draw2d.text.ParagraphTextLayout <p>
41
     * 
42
     * Given the Bidi levels of the given text, this method breaks the given
43
     * text up by its level runs.
44
     * 
45
     * @param text
46
     *            the String that needs to be broken up into its level runs
47
     * @param levelInfo
48
     *            the Bidi levels
49
     * @return the requested segment
50
     */
51
    private String[] getSegments(String text, int levelInfo[]) {
52
        if (levelInfo.length == 1)
53
            return new String[] {text};
54
55
        String result[] = new String[levelInfo.length / 2 + 1];
56
57
        int i;
58
        int endOffset;
59
        int beginOffset = 0;
60
61
        for (i = 0; i < result.length - 1; i++) {
62
            endOffset = levelInfo[i * 2 + 1];
63
            result[i] = text.substring(beginOffset, endOffset);
64
            beginOffset = endOffset;
65
        }
66
        endOffset = text.length();
67
        result[i] = text.substring(beginOffset, endOffset);
68
        return result;
69
    }
70
    
71
    /** 
72
     * Note: This class is a direct copy from ParagraphTextLayout
73
     */
74
    class SegmentLookahead
75
        implements LookAhead {
76
77
        private int seg = -1;
78
79
        private String segs[];
80
81
        private int[] width;
82
83
        private final int trailingBorderSize;
84
85
        private FlowContext context;
86
87
        private FlowFigure flowFigure;
88
89
        public SegmentLookahead(String segs[], int trailingBorderSize,
90
                FlowContext context, FlowFigure flowFigure) {
91
            this.segs = segs;
92
            this.trailingBorderSize = trailingBorderSize;
93
            this.context = context;
94
            this.flowFigure = flowFigure;
95
        }
96
97
        public int getWidth() {
98
            if (width == null) {
99
                width = new int[1];
100
                int startingIndex = seg + 1;
101
                TextFlow textFlow = (TextFlow) flowFigure;
102
103
                if (startingIndex == segs.length) {
104
                    width[0] += trailingBorderSize;
105
                    context.getWidthLookahead(textFlow, width);
106
                } else {
107
                    String rest = segs[startingIndex];
108
                    for (int k = startingIndex + 1; k < segs.length; k++)
109
                        rest += segs[k];
110
                    if (!textFlow.addLeadingWordWidth(rest, width)) {
111
                        width[0] += trailingBorderSize;
112
                        context.getWidthLookahead(textFlow, width);
113
                    }
114
                }
115
            }
116
            return width[0];
117
        }
118
119
        public void setIndex(int value) {
120
            this.seg = value;
121
            width = null;
122
        }
123
    }
124
125
    /**
126
     * Note: This method is a direct copy from
127
     * org.eclipse.draw2d.text.ParagraphTextLayout but takes into account
128
     * the map mode and uses methods from FlowUtilitiesEx <p>
129
     */
130
    protected void layout() {
131
        TextFlow textFlow = (TextFlow) getFlowFigure();
132
        int offset = 0;
133
134
        FlowContext context = getContext();
135
        List fragments = textFlow.getFragments();
136
        Font font = textFlow.getFont();
137
        int fragIndex = 0;
138
        int advance = 0;
139
140
        TextFragmentBox fragment;
141
        int levelInfo[] = (textFlow.getBidiInfo() == null) ? new int[] {-1}
142
            : textFlow.getBidiInfo().levelInfo;
143
144
        String segment, segments[] = getSegments(textFlow.getText(),
145
            levelInfo);
146
        FlowBorder border = null;
147
        if (textFlow.getBorder() instanceof FlowBorder)
148
            border = (FlowBorder) textFlow.getBorder();
149
150
        SegmentLookahead lookahead = new SegmentLookahead(
151
            segments, border == null ? 0
152
                : mm.DPtoLP(border.getRightMargin()), getContext(),
153
            getFlowFigure());
154
        int seg;
155
156
        if (border != null) {
157
            fragment = getFragment(fragIndex++, fragments);
158
            fragment.setBidiLevel(levelInfo[0]);
159
            fragment.setTruncated(false);
160
            fragment.offset = fragment.length = -1;
161
            fragment.setWidth(mm.DPtoLP(border.getLeftMargin())
162
                + mm.DPtoLP(border.getInsets(textFlow).left));
163
            if (context.getRemainingLineWidth() < fragment.getWidth()
164
                + lookahead.getWidth())
165
                context.endLine();
166
            context.addToCurrentLine(fragment);
167
        }
168
169
        for (seg = 0; seg < segments.length; seg++) {
170
            segment = segments[seg];
171
            lookahead.setIndex(seg);
172
173
            do {
174
                fragment = getFragment(fragIndex++, fragments);
175
176
                fragment.offset = offset;
177
                fragment.setBidiLevel(levelInfo[seg * 2]);
178
179
                advance = FlowUtilitiesEx.wrapFragmentInContext(fragment,
180
                    segment, context, lookahead, font, getWrappingStyle(), mm);
181
                segment = segment.substring(advance);
182
                offset += advance;
183
                if ((segment.length() > 0 || fragment.length < advance)
184
                    || fragment.isTruncated())
185
                    context.endLine();
186
            } while (segment.length() > 0
187
                || (!fragment.isTruncated() && fragment.length < advance));
188
        }
189
190
        if (border != null) {
191
            fragment = getFragment(fragIndex++, fragments);
192
            fragment.setBidiLevel(levelInfo[0]);
193
            fragment.setTruncated(false);
194
            fragment.offset = fragment.length = -1;
195
            fragment.setWidth(mm.DPtoLP(border.getRightMargin())
196
                + mm.DPtoLP(border.getInsets(textFlow).right));
197
            context.addToCurrentLine(fragment);
198
        }
199
200
        // Remove the remaining unused fragments.
201
        while (fragIndex < fragments.size())
202
            fragments.remove(fragments.size() - 1);
203
    }
204
205
    /* (non-Javadoc)
206
     * @see org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode#setMapMode(org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode)
207
     */
208
    public void setMapMode(IMapMode mm) {
209
        this.mm = mm;
210
    }
211
212
    /**
213
     * Constructs a new ParagraphTextLayout on the specified TextFlow.
214
     * 
215
     * @param flow
216
     *            the TextFlow
217
     */
218
    public ParagraphTextLayoutEx(TextFlow flow) {
219
        super(flow);
220
    }
221
222
    /**
223
     * Constructs the layout with the specified TextFlow and wrapping style.
224
     * The wrapping style must be one of:
225
     * <UL>
226
     * <LI>{@link #WORD_WRAP_HARD}</LI>
227
     * <LI>{@link #WORD_WRAP_SOFT}</LI>
228
     * <LI>{@link #WORD_WRAP_TRUNCATE}</LI>
229
     * </UL>
230
     * 
231
     * @param flow
232
     *            the textflow
233
     * @param style
234
     *            the style of wrapping
235
     */
236
    public ParagraphTextLayoutEx(TextFlow flow, int style) {
237
        super(flow, style);
238
    }
239
    
240
}
(-)src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SelectableTextFlow.java (+536 lines)
Added Link Here
1
/******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *    IBM Corporation - initial API and implementation 
10
 ****************************************************************************/
11
12
package org.eclipse.gmf.runtime.draw2d.ui.internal.text;
13
14
import java.util.List;
15
16
import org.eclipse.draw2d.ColorConstants;
17
import org.eclipse.draw2d.FigureUtilities;
18
import org.eclipse.draw2d.Graphics;
19
import org.eclipse.draw2d.geometry.Point;
20
import org.eclipse.draw2d.geometry.Rectangle;
21
import org.eclipse.draw2d.text.BidiChars;
22
import org.eclipse.draw2d.text.CaretInfo;
23
import org.eclipse.draw2d.text.TextFlow;
24
import org.eclipse.draw2d.text.TextFragmentBox;
25
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
26
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
27
import org.eclipse.swt.SWT;
28
import org.eclipse.swt.graphics.TextLayout;
29
30
/**
31
 * Text flow with the following capabilities: <br>
32
 * 1. Selectable, focusable, <br>
33
 * 2. text can have underline, strikethrough <br>
34
 * 3. return total height or visible height of text.
35
 * 
36
 * @author satif
37
 * 
38
 */
39
public class SelectableTextFlow
40
    extends TextFlow {
41
42
    // flag to indicate text selection
43
    private static int FLAG_SELECTED = MAX_FLAG << 1;
44
45
    // flag to indicate if the text has a focus rectangle 
46
    // drawn around it
47
    private static int FLAG_HASFOCUS = MAX_FLAG << 2;
48
49
    // flag to indicate if text should be underlined
50
    private static int FLAG_UNDERLINED = MAX_FLAG << 3;
51
52
    // flag to indicate if text should be striked through 
53
    private static int FLAG_STRIKEDTHROUGH = MAX_FLAG << 4;
54
55
    // map mode constants cached...
56
    private int nDPtoLP_1 = 1, nDPtoLP_2 = 2;
57
58
    private IMapMode mm = MapModeUtil.getMapMode();
59
60
    // the literal bounds of the text excluding any extra space occupied
61
    //  by the TextFlow. These bounds are like the union of all fragments.
62
    private Rectangle literal_SubstringTextBounds;
63
64
    
65
    /**
66
     * Note: this method is overridden to allow the use of FlowUtilitiesEx methods 
67
     * with map mode. <p>
68
     * 
69
     * @see org.eclipse.draw2d.text.TextFlow#addLeadingWordWidth(java.lang.String, int[])
70
     */
71
    public boolean addLeadingWordWidth(String text, int[] width) {
72
        if (text.length() == 0)
73
            return false;
74
        if (Character.isWhitespace(text.charAt(0)))
75
            return true;
76
77
        text = 'a' + text + 'a';
78
        FlowUtilitiesEx.LINE_BREAK.setText(text);
79
        int index = FlowUtilitiesEx.LINE_BREAK.next() - 1;
80
        if (index == 0)
81
            return true;
82
        while (Character.isWhitespace(text.charAt(index)))
83
            index--;
84
        boolean result = index < text.length() - 1;
85
        // index should point to the end of the actual text (not including the
86
        // 'a' that was
87
        // appended), if there were no breaks
88
        if (index == text.length() - 1)
89
            index--;
90
        text = text.substring(1, index + 1);
91
92
        if (getBidiInfo() == null)
93
            width[0] += FlowUtilitiesEx.getStringExtents(text, getFont()).width;
94
        else {
95
            TextLayout textLayout = FlowUtilitiesEx.getTextLayout();
96
            textLayout.setFont(getFont());
97
            textLayout.setText(text);
98
            width[0] += textLayout.getBounds().width;
99
        }
100
        return result;
101
    }
102
103
    /**
104
     * Calculates literal_SubstringTextBounds.
105
     * This is the literal bounds of the text excluding any extra space occupied
106
     * by the TextFlow. These bounds are like the union of all fragments.
107
     */
108
    private void calculateBounds() {
109
        if (literal_SubstringTextBounds != null)
110
            return;
111
112
        List list = getFragments();
113
        TextFragmentBox box;
114
        int left = Integer.MAX_VALUE, top = left;
115
        int right = Integer.MIN_VALUE, bottom = right;
116
117
        for (int i = 0; i < list.size(); i++) {
118
            box = (TextFragmentBox) list.get(i);
119
            left = Math.min(left, box.getX());
120
121
            // The +2 is for leading characters...
122
            right = Math.max(right, box.getX() + box.getWidth() + nDPtoLP_2);
123
124
            top = Math.min(top, box.getLineRoot().getVisibleTop());
125
            bottom = Math.max(bottom, box.getLineRoot().getVisibleBottom());
126
        }
127
        if (literal_SubstringTextBounds == null)
128
            literal_SubstringTextBounds = new Rectangle(left, top,
129
                right - left, bottom - top);
130
        else {
131
            literal_SubstringTextBounds.x = left;
132
            literal_SubstringTextBounds.y = top;
133
            literal_SubstringTextBounds.width = right - left;
134
            literal_SubstringTextBounds.height = bottom - top;
135
        }
136
    }
137
138
    /*
139
     * (non-Javadoc)
140
     * 
141
     * @see org.eclipse.draw2d.text.TextFlow#getAscent()
142
     */
143
    public int getAscent() {
144
        return mm.DPtoLP(super.getAscent());
145
    }
146
147
    /**
148
     * Note: this method is directly copied from
149
     * org.eclipse.draw2d.text.TextFlow <p>
150
     * 
151
     * @param box
152
     *            which fragment
153
     * @param index
154
     *            the fragment index
155
     * @return the bidi string for that fragment
156
     */
157
    private String getBidiSubstring(TextFragmentBox box, int index) {
158
        if (box.getBidiLevel() < 1)
159
            return getText().substring(box.offset, box.offset + box.length);
160
161
        StringBuffer buffer = new StringBuffer(box.length + 3);
162
        buffer.append(box.isRightToLeft() ? BidiChars.RLO
163
            : BidiChars.LRO);
164
        if (index == 0 && getBidiInfo().leadingJoiner)
165
            buffer.append(BidiChars.ZWJ);
166
        buffer.append(getText().substring(box.offset, box.offset + box.length));
167
        if (index == getFragments().size() - 1 && getBidiInfo().trailingJoiner)
168
            buffer.append(BidiChars.ZWJ);
169
        return buffer.toString();
170
    }
171
172
    /*
173
     * (non-Javadoc)
174
     * 
175
     * @see org.eclipse.draw2d.text.TextFlow#getDescent()
176
     */
177
    public int getDescent() {
178
        return mm.DPtoLP(super.getDescent());
179
    }
180
181
    /**
182
     * Returns the bounds of visible text.
183
     * 
184
     * @return Rectangle bounds of visible text.
185
     */
186
    public Rectangle getLiteralSubstringTextBounds() {
187
        calculateBounds();
188
        return literal_SubstringTextBounds;
189
    }
190
    
191
    /**
192
     * Note: this method is directly copied from
193
     * org.eclipse.draw2d.text.TextFlow
194
     */
195
    private int getBidiPrefixLength(TextFragmentBox box, int index) {
196
        if (box.getBidiLevel() < 1)
197
            return 0;
198
        if (index > 0 || !getBidiInfo().leadingJoiner)
199
            return 1;
200
        return 2;
201
    }
202
203
    /**
204
     * Note: this method is directly copied from
205
     * org.eclipse.draw2d.text.TextFlow but takes into account
206
     * the map mode and uses methods from FlowUtilitiesEx <p>
207
     * 
208
     * @see org.eclipse.draw2d.text.TextFlow#getPointInBox(org.eclipse.draw2d.text.TextFragmentBox,
209
     *      int, int, boolean)
210
     */
211
    protected Point getPointInBox(TextFragmentBox box, int offset, int index,
212
            boolean trailing) {        
213
        offset -= box.offset;
214
        offset = Math.min(box.length, offset);
215
        Point result = new Point(0, box.getBaseline() - box.getAscent());
216
        if (getBidiInfo() == null) {
217
            if (trailing && offset < box.length)
218
                offset++;
219
            String substring = getText().substring(box.offset, box.offset + offset);
220
            result.x = mm.DPtoLP(FigureUtilities.getStringExtents(
221
                substring, getFont()).width);
222
        } else {
223
            TextLayout layout = FlowUtilitiesEx.getTextLayout();
224
            layout.setFont(getFont());
225
            String fragString = getBidiSubstring(box, index);
226
            layout.setText(fragString);
227
            offset += getBidiPrefixLength(box, index);
228
            result.x = layout.getLocation(offset, trailing).x;
229
            if (isMirrored())
230
                result.x = box.getWidth() - result.x;
231
        }
232
        result.x += box.getX();
233
        return result;
234
    }
235
236
    /**
237
     * @return the focus state of this label
238
     */
239
    public boolean hasFocus() {
240
        return (flags & FLAG_HASFOCUS) != 0;
241
    }
242
243
    /*
244
     * (non-Javadoc)
245
     * 
246
     * @see org.eclipse.draw2d.Figure#invalidate()
247
     */
248
    public void invalidate() {
249
        super.invalidate();
250
        literal_SubstringTextBounds = null;
251
    }
252
253
    /**
254
     * @return the selection state of this label
255
     */
256
    public boolean isSelected() {
257
        return (flags & FLAG_SELECTED) != 0;
258
    }
259
260
    /**
261
     * @return whether the label text is striked-through
262
     */
263
    public boolean isTextStrikeThrough() {
264
        return (flags & FLAG_STRIKEDTHROUGH) != 0;
265
    }
266
267
    /**
268
     * @return whether the label text is underlined
269
     */
270
    public boolean isTextUnderlined() {
271
        return (flags & FLAG_UNDERLINED) != 0;
272
    }
273
274
    /**
275
     * @see org.eclipse.draw2d.Figure#paintFigure(Graphics)
276
     */
277
    protected void paintFigure(Graphics g) {
278
        TextFragmentBox frag;
279
        g.getClip(Rectangle.SINGLETON);
280
        int yStart = Rectangle.SINGLETON.y;
281
        int yEnd = Rectangle.SINGLETON.bottom();
282
283
        int i;
284
285
        // first we calculate the literal size of the visible text and do the
286
        // selection painting (note we have to do this loop, cannot merge
287
        // it in the next one).
288
        calculateBounds();
289
290
        // draw the selection background, if applicable...
291
        if (isSelected()) {
292
            g.pushState();
293
            g.setBackgroundColor(ColorConstants.menuBackgroundSelected);
294
            g.fillRectangle(literal_SubstringTextBounds);
295
            g.popState();
296
            g.setForegroundColor(ColorConstants.white);
297
        } else
298
            g.setForegroundColor(getForegroundColor());
299
300
        // draw the focus rectangle, if applicable...
301
        if (hasFocus()) {
302
            // focus drawing is done manually, as g.drawFocus gives
303
            // incorrect results in this case.
304
            g.pushState();
305
            g.setXORMode(true);
306
            g.setLineWidth(0);
307
            g.setLineStyle(SWT.LINE_DOT);
308
            g.setLineDash(new int[] {1, 2});
309
            g.drawRectangle(literal_SubstringTextBounds.getCopy()
310
                .resize(-1, -1));
311
            g.popState();
312
313
            // restore line dash as it is not restored on pop
314
            g.setLineDash(new int[] {});
315
        }
316
317
        /////////////////////////////////////////////////////////////////////////
318
        // START: Copy from org.eclipse.draw2d.text.TextFlow#paintFigure ////////
319
        
320
        for (i = 0; i < getFragments().size(); i++) {
321
            frag = (TextFragmentBox) getFragments().get(i);
322
            // g.drawLine(frag.getX(), frag.getLineRoot().getVisibleTop(),
323
            //          frag.getWidth() + frag.getX(), frag.getLineRoot().getVisibleTop());
324
            // g.drawLine(frag.getX(), frag.getBaseline(), frag.getWidth() + frag.getX(), frag.getBaseline());
325
            if (frag.offset == -1)
326
                continue;
327
            // Loop until first visible fragment
328
            if (yStart > frag.getLineRoot().getVisibleBottom() + nDPtoLP_1)// The + 1 is for
329
                // disabled text
330
                continue;
331
            // Break loop at first non-visible fragment
332
            if (yEnd < frag.getLineRoot().getVisibleTop())
333
                break;
334
335
            String draw = getBidiSubstring(frag, i);
336
337
            if (frag.isTruncated())
338
                draw += FlowUtilitiesEx.ELLIPSIS;
339
340
            if (!isEnabled()) {
341
                g.setForegroundColor(ColorConstants.buttonLightest);
342
                paintText(g, draw,
343
                        frag.getX() + nDPtoLP_1,
344
                        frag.getBaseline() - getAscent() + nDPtoLP_1,
345
                        frag.getBidiLevel());
346
                g.setForegroundColor(ColorConstants.buttonDarker);
347
                paintText(g, draw,
348
                        frag.getX(),
349
                        frag.getBaseline() - getAscent(),
350
                        frag.getBidiLevel());
351
                g.setForegroundColor(fgColor);
352
            } else {
353
                paintText(g, draw,
354
                    frag.getX(),
355
                    frag.getBaseline() - getAscent(),
356
                    frag.getBidiLevel());
357
            }
358
            
359
            // END: Copy from org.eclipse.draw2d.text.TextFlow#paintFigure //////////
360
            /////////////////////////////////////////////////////////////////////////
361
362
            
363
            // underline the text, if applicable...
364
            if (isTextUnderlined()) {
365
                g.drawLine(frag.getX(),
366
                        frag.getBaseline() + nDPtoLP_1,
367
                        frag.getWidth() + frag.getX(), 
368
                        frag.getBaseline() + nDPtoLP_1);
369
            }
370
371
            // strike through the text, if applicable...
372
            if (isTextStrikeThrough()) {
373
                int visibleTop = frag.getLineRoot().getVisibleTop() +
374
                                 (frag.getBaseline() + getDescent() +
375
                                         nDPtoLP_2 - 
376
                                         frag.getLineRoot().getVisibleTop()) / 2;
377
                g.drawLine(frag.getX(), 
378
                        visibleTop, 
379
                        frag.getWidth() + frag.getX(), 
380
                        visibleTop);
381
            }
382
        }
383
    }
384
385
    /**
386
     * Note: this method is directly copied from
387
     * org.eclipse.draw2d.text.TextFlow but uses methods from FlowUtilitiesEx <p>
388
     * 
389
     * Paints the String draw at the given x and y location.
390
     */
391
    private void paintText(Graphics g, String draw, int x, int y, int bidiLevel) {
392
        if (bidiLevel == -1) {
393
            g.drawString(draw, x, y);
394
        } else {
395
            TextLayout tl = FlowUtilitiesEx.getTextLayout();
396
            if (isMirrored())
397
                tl.setOrientation(SWT.RIGHT_TO_LEFT);
398
            tl.setFont(g.getFont());
399
            tl.setText(draw);
400
            g.drawTextLayout(tl, x, y);
401
        }
402
    }
403
404
    /**
405
     * Sets the focus state of the text
406
     * 
407
     * @param b
408
     *            true will cause a focus rectangle to be drawn around the text
409
     *            of the Label
410
     */
411
    public void setFocus(boolean b) {
412
        if (hasFocus() == b)
413
            return;
414
        setFlag(FLAG_HASFOCUS, b);
415
        repaint();
416
    }
417
418
    /**
419
     * Sets the map mode.
420
     * @param mm the map mode to set.
421
     */
422
    public void setMapMode(IMapMode mm) {
423
        this.mm = mm;
424
        nDPtoLP_1 = mm.DPtoLP(1);
425
        nDPtoLP_2 = mm.DPtoLP(2);
426
    }
427
428
    /**
429
     * Sets the selection state of the text
430
     * 
431
     * @param b
432
     *            true will cause the label to appear selected
433
     */
434
    public void setSelected(boolean b) {
435
        if (isSelected() == b)
436
            return;
437
        setFlag(FLAG_SELECTED, b);
438
        repaint();
439
    }
440
441
    /**
442
     * Sets whether the text should be striked-through
443
     * 
444
     * @param b
445
     *            Whether the label text should be striked-through
446
     */
447
    public void setStrikeThrough(boolean strikeThrough) {
448
        if (isTextStrikeThrough() == strikeThrough)
449
            return;
450
        setFlag(FLAG_STRIKEDTHROUGH, strikeThrough);
451
        repaint();
452
    }
453
454
    /*
455
     * (non-Javadoc)
456
     * 
457
     * @see org.eclipse.draw2d.text.TextFlow#setText(java.lang.String)
458
     */
459
    public void setText(String s) {
460
        super.setText(s);
461
        literal_SubstringTextBounds = null;
462
    }
463
464
    /**
465
     * Sets whether the text should be underlined
466
     * 
467
     * @param b
468
     *            Whether the label text should be underlined
469
     */
470
    public void setTextUnderline(boolean underline) {
471
        if (isTextUnderlined() == underline)
472
            return;
473
        setFlag(FLAG_UNDERLINED, underline);
474
        repaint();
475
    }
476
    
477
    /**
478
     * Maps a location (in absolute coordinates) to an
479
     * offset within the text. <p>
480
     *  
481
     * @param location (in absolute coordinates) to map into offset 
482
     * @return the offset within the text.
483
     */
484
    public int getCaretOffset(Point location) {
485
        // do a binary search for the caret location...
486
        
487
        String text = getText();
488
        int min = 0, 
489
            max = text.length(),
490
            mid = -2,
491
            previousMids[] = {-1,-1}; // cache previous results to detect infinite loops
492
        
493
        char midChar = ' ';
494
        
495
        while (max - min > 1) {
496
            
497
            mid = (max + min) / 2;
498
            
499
            midChar = text.charAt(mid);
500
            
501
            CaretInfo caretInfo = getCaretPlacement(mid, false);
502
            
503
            if (caretInfo.getY() + caretInfo.getLineHeight() < location.y)
504
                min = mid;
505
            else if (caretInfo.getY() > location.y)
506
                max = mid;
507
            else {
508
                if (caretInfo.getX() + FigureUtilities.
509
                        getStringExtents(String.valueOf(midChar),
510
                            getFont()).width < location.x)
511
                    min = mid;
512
                else if (caretInfo.getX() > location.x)
513
                    max = mid;
514
                else // we have found the correct match
515
                    return ++mid; // we want a trailing selection.
516
            }
517
            
518
            // detect and prevent infinite loops...
519
            // this works because we don't expect to calculate a mid
520
            // which we have calculated before.
521
            if (mid == previousMids[0] || mid == previousMids[1])
522
                break;
523
            
524
            previousMids[1] = previousMids[0];
525
            previousMids[0] = mid;
526
        }
527
        
528
        
529
        if (midChar == '\n') // last character when the loop ended
530
            --mid; // this happens when the user clicks past the end of line.
531
        else
532
            ++mid; // we want a trailing selection.
533
        
534
        return mid; 
535
    }
536
}
(-)src/org/eclipse/gmf/runtime/diagram/ui/tools/TextDirectEditManager.java (-4 / +80 lines)
Lines 12-17 Link Here
12
12
13
package org.eclipse.gmf.runtime.diagram.ui.tools;
13
package org.eclipse.gmf.runtime.diagram.ui.tools;
14
14
15
import java.lang.reflect.Constructor;
15
import java.util.ArrayList;
16
import java.util.ArrayList;
16
import java.util.Iterator;
17
import java.util.Iterator;
17
import java.util.List;
18
import java.util.List;
Lines 35-40 Link Here
35
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin;
36
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin;
36
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIStatusCodes;
37
import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIStatusCodes;
37
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer;
38
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer;
39
import org.eclipse.gmf.runtime.draw2d.ui.figures.LabelWithTextLayout;
38
import org.eclipse.gmf.runtime.draw2d.ui.figures.WrapLabel;
40
import org.eclipse.gmf.runtime.draw2d.ui.figures.WrapLabel;
39
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
41
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
40
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
42
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
Lines 55-60 Link Here
55
import org.eclipse.swt.graphics.Image;
57
import org.eclipse.swt.graphics.Image;
56
import org.eclipse.swt.graphics.Point;
58
import org.eclipse.swt.graphics.Point;
57
import org.eclipse.swt.graphics.RGB;
59
import org.eclipse.swt.graphics.RGB;
60
import org.eclipse.swt.widgets.Composite;
58
import org.eclipse.swt.widgets.Control;
61
import org.eclipse.swt.widgets.Control;
59
import org.eclipse.swt.widgets.Display;
62
import org.eclipse.swt.widgets.Display;
60
import org.eclipse.swt.widgets.Event;
63
import org.eclipse.swt.widgets.Event;
Lines 129-134 Link Here
129
        }
132
        }
130
133
131
        public void relocate(CellEditor celleditor) {
134
        public void relocate(CellEditor celleditor) {
135
//            Text text = (Text) celleditor.getControl();
136
//            WrapLabel fig = getWrapLabel();
137
//            
138
//            Rectangle rect = fig.getTextBounds().getCopy();
139
//            fig.translateToAbsolute(rect);            
140
//
141
//            int avrWidth = FigureUtilities.getFontMetrics(text.getFont())
142
//                .getAverageCharWidth();
143
//
144
//            if (fig.isTextWrapped() && fig.getText().length() > 0)
145
//                rect.setSize(new Dimension(rect.width + avrWidth * 2, rect.height));
146
//            else
147
//                rect.setSize(new Dimension(text.computeSize(SWT.DEFAULT,
148
//                    SWT.DEFAULT)).expand(avrWidth * 2, 0));
149
//            
150
//            org.eclipse.swt.graphics.Rectangle newRect =
151
//                 text.computeTrim(rect.x, rect.y, rect.width, rect.height);
152
//            text.setBounds(newRect);
153
            
154
            
155
            // TODO: find a common relocate method for all labels.
156
            
132
            Text text = (Text) celleditor.getControl();
157
            Text text = (Text) celleditor.getControl();
133
            WrapLabel fig = getWrapLabel();
158
            WrapLabel fig = getWrapLabel();
134
            
159
            
Lines 234-239 Link Here
234
        return TextCellEditorEx.class;
259
        return TextCellEditorEx.class;
235
    }
260
    }
236
    
261
    
262
    protected CellEditor createCellEditorOn(Composite composite) {
263
        
264
        Class ceClass = getTextCellEditorClass(getEditPart());
265
        
266
        if (ceClass == TextCellEditorEx.class ||
267
            ceClass == WrapTextCellEditor.class) {
268
            
269
            try {
270
                
271
                Constructor constructor = ceClass.getConstructor(new Class[]{Composite.class, 
272
                    int.class});
273
                
274
                int style;
275
                
276
                if (ceClass == TextCellEditorEx.class)
277
                    style = SWT.SINGLE;
278
                else
279
                    style = SWT.MULTI | SWT.WRAP;
280
                
281
                IFigure figure = getEditPart().getFigure();
282
                
283
                if (figure instanceof WrapLabel) {
284
                    if (((WrapLabel)figure).getTextWrapAlignment() == PositionConstants.CENTER)
285
                        style |= SWT.CENTER;
286
                    else if (((WrapLabel)figure).getTextWrapAlignment() == PositionConstants.RIGHT)
287
                        style |= SWT.RIGHT;
288
                }
289
                    
290
                Integer param1 = new Integer(style);
291
                
292
                return (CellEditor)constructor.newInstance(new Object[]{composite, param1});
293
                
294
            } catch (Exception e) {
295
                return null;
296
            }
297
        }
298
        else {
299
            return super.createCellEditorOn(composite);
300
        }
301
    }
237
302
238
    /**
303
    /**
239
     * Given a <code>WrapLabel</code> object, this will calculate the 
304
     * Given a <code>WrapLabel</code> object, this will calculate the 
Lines 535-549 Link Here
535
        String text;
600
        String text;
536
601
537
        IFigure fig = getEditPart().getFigure();
602
        IFigure fig = getEditPart().getFigure();
538
        if (fig instanceof WrapLabel) {
603
        if (fig instanceof LabelWithTextLayout) {
539
            WrapLabel label = (WrapLabel) fig;
604
            int caretPos = ((LabelWithTextLayout) fig)
605
                .getCaretOffset(new org.eclipse.draw2d.geometry.Point(
606
                    location.x, location.y));
607
            Text textControl = (Text) getCellEditor().getControl();
608
            if (caretPos == -1)
609
                textControl.setSelection(0, textControl.getText().length());
610
            else
611
                textControl.setSelection(caretPos);
612
613
            return;
614
        } else if (fig instanceof Label) {
615
            Label label = (Label) fig;
540
            iconBounds = label.getIconBounds().getCopy();
616
            iconBounds = label.getIconBounds().getCopy();
541
            icon = label.getIcon();
617
            icon = label.getIcon();
542
            textPlacement = label.getTextPlacement();
618
            textPlacement = label.getTextPlacement();
543
            subStringText = label.getSubStringText();
619
            subStringText = label.getSubStringText();
544
            text = label.getText();
620
            text = label.getText();
545
        } else if (fig instanceof Label) {
621
        } else if (fig instanceof WrapLabel) {
546
            Label label = (Label) fig;
622
            WrapLabel label = (WrapLabel) fig;
547
            iconBounds = label.getIconBounds().getCopy();
623
            iconBounds = label.getIconBounds().getCopy();
548
            icon = label.getIcon();
624
            icon = label.getIcon();
549
            textPlacement = label.getTextPlacement();
625
            textPlacement = label.getTextPlacement();

Return to bug 162932