1 /*******************************************************************************
2  * Copyright (c) 2012, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  ******************************************************************************/
14 
15 package org.eclipse.jface.util;
16 
17 import java.util.HashMap;
18 import java.util.Map;
19 
20 import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory;
21 import org.eclipse.jface.internal.InternalPolicy;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.custom.StyledText;
24 import org.eclipse.swt.events.SegmentListener;
25 import org.eclipse.swt.widgets.Combo;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.swt.widgets.Control;
28 import org.eclipse.swt.widgets.Text;
29 
30 /**
31  * This class provides API to handle Base Text Direction (BTD) and
32  * Structured Text support for SWT Text widgets.
33  *
34  * @since 3.9
35  */
36 public final class BidiUtils {
37 
38 	/**
39 	 * Left-To-Right Base Text Direction.
40 	 * @see #getTextDirection()
41 	 */
42 	public static final String LEFT_TO_RIGHT = "ltr"; //$NON-NLS-1$
43 
44 	/**
45 	 * Right-To-Left Base Text Direction.
46 	 * @see #getTextDirection()
47 	 */
48 	public static final String RIGHT_TO_LEFT = "rtl";//$NON-NLS-1$
49 
50 	/**
51 	 * Auto (contextual) Base Text Direction.
52 	 * @see #getTextDirection()
53 	 */
54 	public static final String AUTO = "auto";//$NON-NLS-1$
55 
56 	/**
57 	 * Base Text Direction defined in {@link BidiUtils#getTextDirection()}
58 	 * @see #getSegmentListener(String)
59 	 * @see #applyBidiProcessing(Text, String)
60 	 */
61 	public static final String BTD_DEFAULT = "default";//$NON-NLS-1$
62 
63 	/**
64 	 * Visual Left-To-Right Text Direction.
65 	 * <p>
66 	 * <b>Note:</b> This handling type is deprecated and should only be used
67 	 * when interfacing with legacy systems that store data in visual order.
68 	 *
69 	 * @see <a
70 	 *      href="http://www.w3.org/International/questions/qa-visual-vs-logical">http://www.w3.org/International/questions/qa-visual-vs-logical</a>
71 	 * @see #getSegmentListener(String)
72 	 * @see #applyBidiProcessing(Text, String)
73 	 *
74 	 * @since 3.11
75 	 */
76 	public static final String VISUAL_LEFT_TO_RIGHT = "visualltr"; //$NON-NLS-1$
77 
78 	/**
79 	 * Visual Right-To-Left Text Direction
80 	 * <p>
81 	 * <b>Note:</b> This handling type is deprecated and should only be used
82 	 * when interfacing with legacy systems that store data in visual order.
83 	 *
84 	 * @see <a
85 	 *      href="http://www.w3.org/International/questions/qa-visual-vs-logical">http://www.w3.org/International/questions/qa-visual-vs-logical</a>
86 	 * @see #getSegmentListener(String)
87 	 * @see #applyBidiProcessing(Text, String)
88 	 *
89 	 * @since 3.11
90 	 */
91 	public static final String VISUAL_RIGHT_TO_LEFT = "visualrtl";//$NON-NLS-1$
92 
93 	/**
94 	 * Segment listener for LTR Base Text Direction
95 	 */
96 	private static final SegmentListener BASE_TEXT_DIRECTION_LTR = new BaseTextDirectionSegmentListener(LEFT_TO_RIGHT);
97 
98 	/**
99 	 * Segment listener for RTL Base Text Direction
100 	 */
101 	private static final SegmentListener BASE_TEXT_DIRECTION_RTL = new BaseTextDirectionSegmentListener(RIGHT_TO_LEFT);
102 
103 
104 	/**
105 	 * Segment listener for Auto (Contextual) Base Text Direction
106 	 */
107 	private static final SegmentListener BASE_TEXT_DIRECTION_AUTO = new BaseTextDirectionSegmentListener(AUTO);
108 
109 	/**
110 	 * Segment listener for LTR Visual Text Direction
111 	 */
112 	private static final SegmentListener VISUAL_TEXT_DIRECTION_LTR = new VisualTextDirectionSegmentListener(
113 			VISUAL_LEFT_TO_RIGHT);
114 
115 	/**
116 	 * Segment listener for RTL Visual Text Direction
117 	 */
118 	private static final SegmentListener VISUAL_TEXT_DIRECTION_RTL = new VisualTextDirectionSegmentListener(
119 			VISUAL_RIGHT_TO_LEFT);
120 
121 	/**
122 	 * Listener cache. Map from structured text type id ({@link String}) to
123 	 * structured text segment listener ({@link SegmentListener}).
124 	 */
125 	private static final Map<String, SegmentListener> structuredTextSegmentListeners = new HashMap<>();
126 
127 	/**
128 	 * The LRE char
129 	 */
130 	static final char LRE = 0x202A;
131 
132 	/**
133 	 * The LRM char
134 	 */
135 	static final char LRM = 0x200E;
136 
137 	/**
138 	 * The PDF char
139 	 */
140 	static final char PDF = 0x202C;
141 
142 	/**
143 	 * The RLE char
144 	 */
145 	static final char RLE = 0x202B;
146 
147 	/**
148 	 * The LRO char
149 	 */
150 	static final char LRO = 0x202D;
151 
152 	/**
153 	 * The RLO char
154 	 */
155 	static final char RLO = 0x202E;
156 
157 	private static boolean bidiSupport = false;
158 	private static String textDirection = "";//$NON-NLS-1$
159 
BidiUtils()160 	private BidiUtils() {
161 		// no instances
162 	}
163 
164 	/**
165 	 * Returns the Base Text Direction. Possible values are:
166 	 * <ul>
167 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
168 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
169 	 * <li>{@link BidiUtils#AUTO}</li>
170 	 * <li><code>null</code> (no direction set)</li>
171 	 * </ul>
172 	 *
173 	 * @return the base text direction
174 	 */
getTextDirection()175 	public static String getTextDirection() {
176 		return textDirection;
177 	}
178 
179 	/**
180 	 * Sets the Base Text Direction. Possible values are:
181 	 * <ul>
182 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
183 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
184 	 * <li>{@link BidiUtils#AUTO}</li>
185 	 * <li><code>null</code> (no default direction)</li>
186 	 * </ul>
187 	 *
188 	 * @param direction the text direction to set
189 	 * @throws IllegalArgumentException if <code>direction</code> is not legal
190 	 */
setTextDirection(String direction)191 	public static void setTextDirection(String direction) {
192 		if (direction == null || LEFT_TO_RIGHT.equals(direction) || RIGHT_TO_LEFT.equals(direction) || AUTO.equals(direction)) {
193 			textDirection = direction;
194 		} else {
195 			throw new IllegalArgumentException(direction);
196 		}
197 	}
198 
199 	/**
200 	 * Returns whether bidi support is enabled.
201 	 *
202 	 * @return <code>true</code> iff bidi support is enabled
203 	 */
getBidiSupport()204 	public static boolean getBidiSupport() {
205 		return bidiSupport;
206 	}
207 
208 	/**
209 	 * Enables or disables bidi support.
210 	 *
211 	 * @param bidi <code>true</code> to enable bidi support, <code>false</code> to disable
212 	 */
setBidiSupport(boolean bidi)213 	public static void setBidiSupport(boolean bidi) {
214 		bidiSupport = bidi;
215 	}
216 
217 	/**
218 	 * Applies bidi processing to the given text field.
219 	 *
220 	 * <p>
221 	 * Possible values for <code>handlingType</code> are:
222 	 * <ul>
223 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
224 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
225 	 * <li>{@link BidiUtils#AUTO}</li>
226 	 * <li>{@link BidiUtils#BTD_DEFAULT}</li>
227 	 * <li>{@link BidiUtils#VISUAL_LEFT_TO_RIGHT}</li>
228 	 * <li>{@link BidiUtils#VISUAL_RIGHT_TO_LEFT}</li>
229 	 * <li>the <code>String</code> constants in
230 	 * {@link StructuredTextTypeHandlerFactory}</li>
231 	 * <li>if OSGi is running, the types that have been contributed to the
232 	 * <code>org.eclipse.equinox.bidi.bidiTypes</code> extension point.</li>
233 	 * </ul>
234 	 * <p>
235 	 * The 3 values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT}, and
236 	 * {@link #AUTO} are usable whether {@link #getBidiSupport() bidi support}
237 	 * is enabled or disabled.
238 	 * <p>
239 	 * The remaining values only have an effect if bidi support is enabled.
240 	 * <p>
241 	 * The 4 first values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT},
242 	 * {@link #AUTO}, and {@link #BTD_DEFAULT} are for Base Text Direction (BTD)
243 	 * handling. The remaining values are for Structured Text handling.
244 	 * <p>
245 	 * <strong>Note:</strong> If this method is called on a text control, then
246 	 * {@link #applyTextDirection(Control, String)} must not be called on the
247 	 * same control.
248 	 * <p>
249 	 * <strong>Note:</strong> The Structured Text handling only works if the
250 	 * <code>org.eclipse.equinox.bidi</code> bundle is on the classpath!
251 	 * </p>
252 	 *
253 	 * <p>
254 	 * <strong>Note:</strong>
255 	 * {@link org.eclipse.swt.widgets.Text#addSegmentListener(SegmentListener)}
256 	 * is currently only implemented on Windows and GTK, so this method won't
257 	 * have an effect on Cocoa.
258 	 *
259 	 * @param field
260 	 *            the text field
261 	 * @param handlingType
262 	 *            the type of handling
263 	 * @throws IllegalArgumentException
264 	 *             if <code>handlingType</code> is not a known type identifier
265 	 */
applyBidiProcessing(Text field, String handlingType)266 	public static void applyBidiProcessing(Text field, String handlingType) {
267 		SegmentListener listener = getSegmentListener(handlingType);
268 		if (listener != null) {
269 			field.addSegmentListener(listener);
270 			if (InternalPolicy.DEBUG_BIDI_UTILS) {
271 				int color = 0;
272 				if (LEFT_TO_RIGHT.equals(handlingType)) {
273 					color = SWT.COLOR_RED;
274 				} else if (RIGHT_TO_LEFT.equals(handlingType)) {
275 					color = SWT.COLOR_GREEN;
276 				} else if (BTD_DEFAULT.equals(handlingType)) {
277 					color = SWT.COLOR_YELLOW;
278 				} else if (AUTO.equals(handlingType)) {
279 					color = SWT.COLOR_MAGENTA;
280 				} else {
281 					color = SWT.COLOR_CYAN;
282 				}
283 				field.setBackground(field.getDisplay().getSystemColor(color));
284 				if (field.getMessage().length() == 0) {
285 					field.setMessage('<' + handlingType + '>');
286 				}
287 				if (field.getToolTipText() == null) {
288 					field.setToolTipText('<' + handlingType + '>');
289 				}
290 			}
291 		}
292 	}
293 
294 	/**
295 	 * Applies bidi processing to the given styled text field.
296 	 *
297 	 * <p>
298 	 * Possible values for <code>handlingType</code> are:
299 	 * <ul>
300 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
301 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
302 	 * <li>{@link BidiUtils#AUTO}</li>
303 	 * <li>{@link BidiUtils#BTD_DEFAULT}</li>
304 	 * <li>{@link BidiUtils#VISUAL_LEFT_TO_RIGHT}</li>
305 	 * <li>{@link BidiUtils#VISUAL_RIGHT_TO_LEFT}</li>
306 	 * <li>the <code>String</code> constants in
307 	 * {@link StructuredTextTypeHandlerFactory}</li>
308 	 * <li>if OSGi is running, the types that have been contributed to the
309 	 * <code>org.eclipse.equinox.bidi.bidiTypes</code> extension point.</li>
310 	 * </ul>
311 	 * <p>
312 	 * The 3 values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT}, and
313 	 * {@link #AUTO} are usable whether {@link #getBidiSupport() bidi support}
314 	 * is enabled or disabled.
315 	 * <p>
316 	 * The remaining values only have an effect if bidi support is enabled.
317 	 * <p>
318 	 * The 4 first values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT},
319 	 * {@link #AUTO}, and {@link #BTD_DEFAULT} are for Base Text Direction (BTD)
320 	 * handling. The remaining values are for Structured Text handling.
321 	 * <p>
322 	 * <strong>Note:</strong> If this method is called on a text control, then
323 	 * {@link #applyTextDirection(Control, String)} must not be called on the
324 	 * same control.
325 	 * <p>
326 	 * <strong>Note:</strong> The Structured Text handling only works if the
327 	 * <code>org.eclipse.equinox.bidi</code> bundle is on the classpath!
328 	 * </p>
329 	 *
330 	 * @param field
331 	 *            the styled text field
332 	 * @param handlingType
333 	 *            the type of handling
334 	 * @throws IllegalArgumentException
335 	 *             if <code>handlingType</code> is not a known type identifier
336 	 */
applyBidiProcessing(StyledText field, String handlingType)337 	public static void applyBidiProcessing(StyledText field, String handlingType) {
338 		final SegmentListener listener = getSegmentListener(handlingType);
339 		if (listener != null) {
340 			field.addBidiSegmentListener(listener::getSegments);
341 		}
342 	}
343 
344 	/**
345 	 * Applies bidi processing to the given combo.
346 	 *
347 	 * <p>
348 	 * Possible values for <code>handlingType</code> are:
349 	 * <ul>
350 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
351 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
352 	 * <li>{@link BidiUtils#AUTO}</li>
353 	 * <li>{@link BidiUtils#BTD_DEFAULT}</li>
354 	 * <li>{@link BidiUtils#VISUAL_LEFT_TO_RIGHT}</li>
355 	 * <li>{@link BidiUtils#VISUAL_RIGHT_TO_LEFT}</li>
356 	 * <li>the <code>String</code> constants in
357 	 * {@link StructuredTextTypeHandlerFactory}</li>
358 	 * <li>if OSGi is running, the types that have been contributed to the
359 	 * <code>org.eclipse.equinox.bidi.bidiTypes</code> extension point.</li>
360 	 * </ul>
361 	 * <p>
362 	 * The 3 values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT}, and
363 	 * {@link #AUTO} are usable whether {@link #getBidiSupport() bidi support}
364 	 * is enabled or disabled.
365 	 * <p>
366 	 * The remaining values only have an effect if bidi support is enabled.
367 	 * <p>
368 	 * The 4 first values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT},
369 	 * {@link #AUTO}, and {@link #BTD_DEFAULT} are for Base Text Direction (BTD)
370 	 * handling. The remaining values are for Structured Text handling.
371 	 * <p>
372 	 * <strong>Note:</strong> If this method is called on a combo control, then
373 	 * {@link #applyTextDirection(Control, String)} must not be called on the
374 	 * same control.
375 	 * <p>
376 	 * <strong>Note:</strong> The Structured Text handling only works if the
377 	 * <code>org.eclipse.equinox.bidi</code> bundle is on the classpath!
378 	 * </p>
379 	 *
380 	 * <p>
381 	 * <strong>Note:</strong>
382 	 * {@link org.eclipse.swt.widgets.Combo#addSegmentListener(SegmentListener)}
383 	 * is currently only implemented on Windows so this method won't have an
384 	 * effect on Cocoa and GTK.
385 	 *
386 	 * @param combo
387 	 *            the combo field
388 	 * @param handlingType
389 	 *            the type of handling
390 	 * @throws IllegalArgumentException
391 	 *             if <code>handlingType</code> is not a known type identifier
392 	 * @since 3.10
393 	 */
applyBidiProcessing(Combo combo, String handlingType)394 	public static void applyBidiProcessing(Combo combo, String handlingType) {
395 		SegmentListener listener = getSegmentListener(handlingType);
396 		if (listener != null) {
397 			combo.addSegmentListener(listener);
398 			if (InternalPolicy.DEBUG_BIDI_UTILS) {
399 				int color = 0;
400 				if (LEFT_TO_RIGHT.equals(handlingType)) {
401 					color = SWT.COLOR_RED;
402 				} else if (RIGHT_TO_LEFT.equals(handlingType)) {
403 					color = SWT.COLOR_GREEN;
404 				} else if (BTD_DEFAULT.equals(handlingType)) {
405 					color = SWT.COLOR_YELLOW;
406 				} else if (AUTO.equals(handlingType)) {
407 					color = SWT.COLOR_MAGENTA;
408 				} else {
409 					color = SWT.COLOR_CYAN;
410 				}
411 				combo.setBackground(combo.getDisplay().getSystemColor(color));
412 				if (combo.getToolTipText() == null) {
413 					combo.setToolTipText('<' + handlingType + '>');
414 				}
415 			}
416 		}
417 	}
418 
419 	/**
420 	 * Returns a segment listener for the given <code>handlingType</code> that
421 	 * can e.g. be passed to {@link Text#addSegmentListener(SegmentListener)}.
422 	 *
423 	 * <p>
424 	 * <strong>Note:</strong> The Structured Text handling only works if the
425 	 * <code>org.eclipse.equinox.bidi</code> bundle is on the classpath!
426 	 * </p>
427 	 *
428 	 * @param handlingType
429 	 *            the handling type as specified in
430 	 *            {@link #applyBidiProcessing(Text, String)}
431 	 * @return the segment listener, or <code>null</code> if no handling is
432 	 *         required
433 	 * @throws IllegalArgumentException
434 	 *             if <code>handlingType</code> is not a known type identifier
435 	 * @see #applyBidiProcessing(Text, String)
436 	 */
getSegmentListener(String handlingType)437 	public static SegmentListener getSegmentListener(String handlingType) {
438 		SegmentListener listener = null;
439 		if (LEFT_TO_RIGHT.equals(handlingType)) {
440 			listener = BASE_TEXT_DIRECTION_LTR;
441 		} else if (RIGHT_TO_LEFT.equals(handlingType)) {
442 			listener = BASE_TEXT_DIRECTION_RTL;
443 		} else if (AUTO.equals(handlingType)) {
444 			listener = BASE_TEXT_DIRECTION_AUTO;
445 
446 		} else if (getBidiSupport()) {
447 			if (BTD_DEFAULT.equals(handlingType)) {
448 				if (LEFT_TO_RIGHT.equals(getTextDirection())) {
449 					listener = BASE_TEXT_DIRECTION_LTR;
450 				} else if (RIGHT_TO_LEFT.equals(getTextDirection())) {
451 					listener = BASE_TEXT_DIRECTION_RTL;
452 				} else if (AUTO.equals(getTextDirection())) {
453 					listener = BASE_TEXT_DIRECTION_AUTO;
454 				}
455 			} else if (VISUAL_LEFT_TO_RIGHT.equals(handlingType)) {
456 				listener = VISUAL_TEXT_DIRECTION_LTR;
457 			} else if (VISUAL_RIGHT_TO_LEFT.equals(handlingType)) {
458 				listener = VISUAL_TEXT_DIRECTION_RTL;
459 			} else {
460 				Object handler = structuredTextSegmentListeners.get(handlingType);
461 				if (handler != null) {
462 					listener = (SegmentListener) handler;
463 				} else {
464 					listener = new StructuredTextSegmentListener(handlingType);
465 					structuredTextSegmentListeners.put(handlingType, listener);
466 				}
467 			}
468 		}
469 		return listener;
470 	}
471 
472 	/**
473 	 * Applies a Base Text Direction to the given control (and its descendants,
474 	 * if it's a {@link Composite}).
475 	 *
476 	 * <p>
477 	 * Possible values for <code>textDirection</code> are:
478 	 * <ul>
479 	 * <li>{@link BidiUtils#LEFT_TO_RIGHT}</li>
480 	 * <li>{@link BidiUtils#RIGHT_TO_LEFT}</li>
481 	 * <li>{@link BidiUtils#AUTO}</li>
482 	 * <li>{@link BidiUtils#BTD_DEFAULT}</li>
483 	 * </ul>
484 	 * <p>
485 	 * The 3 values {@link #LEFT_TO_RIGHT}, {@link #RIGHT_TO_LEFT}, and
486 	 * {@link BidiUtils#AUTO} are usable whether {@link #getBidiSupport() bidi
487 	 * support} is enabled or disabled.
488 	 * <p>
489 	 * The remaining value {@link BidiUtils#BTD_DEFAULT} only has an effect if
490 	 * bidi support is enabled.
491 	 *
492 	 * <p>
493 	 * <strong>Note:</strong> If this method is called on a control, then no
494 	 * <code>applyBidiProcessing</code> method must be called on the same
495 	 * control.
496 	 * <p>
497 	 * <strong>Note:</strong>
498 	 * {@link org.eclipse.swt.widgets.Control#setTextDirection(int)} is
499 	 * currently only implemented on Windows, so the direction won't be
500 	 * inherited by descendants on GTK and Cocoa.
501 	 * <p>
502 	 *
503 	 * @param control
504 	 *            the control
505 	 * @param textDirection
506 	 *            the text direction
507 	 */
applyTextDirection(Control control, String textDirection)508 	public static void applyTextDirection(Control control, String textDirection) {
509 		int textDir = 0;
510 
511 		if (LEFT_TO_RIGHT.equals(textDirection)) {
512 			textDir = SWT.LEFT_TO_RIGHT;
513 		} else if (RIGHT_TO_LEFT.equals(textDirection)) {
514 			textDir = SWT.RIGHT_TO_LEFT;
515 		} else if (AUTO.equals(textDirection)) {
516 			textDir = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
517 		} else if (getBidiSupport() && BTD_DEFAULT.equals(textDirection)) {
518 			if (LEFT_TO_RIGHT.equals(getTextDirection())) {
519 				textDir = SWT.LEFT_TO_RIGHT;
520 			} else if (RIGHT_TO_LEFT.equals(getTextDirection())) {
521 				textDir = SWT.RIGHT_TO_LEFT;
522 			} else if (AUTO.equals(getTextDirection())) {
523 				textDir = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
524 			}
525 		}
526 
527 		if (control instanceof Text && textDir != 0) {
528 			applyBidiProcessing((Text) control, textDirection);
529 		} else if (control instanceof StyledText && textDir != 0) {
530 			applyBidiProcessing((StyledText) control, textDirection);
531 		} else if (control instanceof Combo && textDir != 0) {
532 			applyBidiProcessing((Combo) control, textDirection);
533 		} else if (textDir != 0) {
534 			control.setTextDirection(textDir);
535 		}
536 	}
537 }
538