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