merged tag ooo/DEV300_m102
[LibreOffice.git] / toolkit / test / accessibility / AccessibleTextHandler.java
blob6fa083b22ea35123aec0474d56603a9004b27276
2 import com.sun.star.accessibility.AccessibleTextType;
3 import com.sun.star.accessibility.TextSegment;
4 import com.sun.star.accessibility.XAccessibleContext;
5 import com.sun.star.accessibility.XAccessibleText;
6 import com.sun.star.accessibility.XAccessibleEditableText;
8 import com.sun.star.awt.Rectangle;
9 import com.sun.star.awt.Point;
10 import com.sun.star.uno.UnoRuntime;
11 import com.sun.star.lang.IndexOutOfBoundsException;
12 import com.sun.star.beans.PropertyValue;
14 import java.util.Vector;
15 import java.awt.Container;
16 import java.awt.FlowLayout;
17 import java.awt.BorderLayout;
18 import java.awt.Color;
19 import java.awt.Component;
20 import java.awt.Graphics;
21 import java.awt.event.ActionListener;
22 import java.awt.event.ActionEvent;
23 import javax.swing.JDialog;
24 import javax.swing.JButton;
25 import javax.swing.JPanel;
26 import javax.swing.JLabel;
27 import javax.swing.Icon;
28 import javax.swing.JTextArea;
29 import javax.swing.JOptionPane;
30 import javax.swing.JCheckBox;
31 import javax.swing.JColorChooser;
32 import javax.swing.BoxLayout;
33 import javax.swing.text.JTextComponent;
36 class AccessibleTextHandler extends NodeHandler
38 public NodeHandler createHandler (XAccessibleContext xContext)
40 XAccessibleText xText = (XAccessibleText) UnoRuntime.queryInterface (
41 XAccessibleText.class, xContext);
42 if (xText != null)
43 return new AccessibleTextHandler (xText);
44 else
45 return null;
48 public AccessibleTextHandler ()
52 public AccessibleTextHandler (XAccessibleText xText)
54 if (xText != null)
55 maChildList.setSize (8);
58 public AccessibleTreeNode createChild (AccessibleTreeNode aParent, int nIndex)
60 AccessibleTreeNode aChild = null;
61 XAccessibleText xText = null;
62 if (aParent instanceof AccTreeNode)
63 xText = ((AccTreeNode)aParent).getText();
65 try
67 if( xText != null )
69 switch( nIndex )
71 case 0:
72 aChild = new StringNode (xText.getText(), aParent);
73 break;
74 case 1:
75 aChild = new StringNode ("# chars: " + xText.getCharacterCount(), aParent);
76 break;
77 case 2:
78 aChild = new StringNode (characters( xText ), aParent);
79 break;
80 case 3:
81 aChild = new StringNode ("selection: "
82 + "[" + xText.getSelectionStart()
83 + "," + xText.getSelectionEnd()
84 + "] \"" + xText.getSelectedText() + "\"",
85 aParent);
86 break;
87 case 4:
88 aChild = new StringNode ("getCaretPosition: " + xText.getCaretPosition(), aParent);
89 break;
90 case 5:
92 VectorNode aVec = new VectorNode("portions", aParent);
93 aChild = aVec;
94 aVec.addChild(
95 textAtIndexNode( xText, "Character",
96 AccessibleTextType.CHARACTER,
97 aParent ) );
98 aVec.addChild(
99 textAtIndexNode( xText, "Word",
100 AccessibleTextType.WORD,
101 aParent ) );
102 aVec.addChild(
103 textAtIndexNode( xText, "Sentence",
104 AccessibleTextType.SENTENCE,
105 aParent ) );
106 aVec.addChild(
107 textAtIndexNode( xText, "Paragraph",
108 AccessibleTextType.PARAGRAPH,
109 aParent ) );
110 aVec.addChild(
111 textAtIndexNode( xText, "Line",
112 AccessibleTextType.LINE,
113 aParent ) );
114 aVec.addChild(
115 textAtIndexNode( xText, "Attribute",
116 AccessibleTextType.ATTRIBUTE_RUN,
117 aParent ) );
118 aVec.addChild(
119 textAtIndexNode( xText, "Glyph",
120 AccessibleTextType.GLYPH,
121 aParent ) );
123 break;
124 case 6:
125 aChild = new StringNode (bounds( xText ), aParent);
126 break;
127 case 7:
128 aChild = getAttributes( xText, aParent );
129 break;
130 default:
131 aChild = new StringNode ("unknown child index " + nIndex, aParent);
135 catch (Exception e)
137 // Return empty child.
140 return aChild;
144 private String textAtIndexNodeString(
145 int nStart, int nEnd,
146 String sWord, String sBefore, String sBehind)
148 return "[" + nStart + "," + nEnd + "] "
149 + "\"" + sWord + "\" \t"
150 + "(" + sBefore + ","
151 + "" + sBehind + ")";
154 /** Create a text node that lists all strings of a particular text type
156 private AccessibleTreeNode textAtIndexNode(
157 XAccessibleText xText,
158 String sName,
159 short nTextType,
160 AccessibleTreeNode aParent)
162 VectorNode aNode = new VectorNode (sName, aParent);
164 // get word at all positions;
165 // for nicer display, compare current word to previous one and
166 // make a new node for every interval, not for every word
167 int nLength = xText.getCharacterCount();
168 if( nLength > 0 )
172 // sWord + nStart mark the current word
173 // make a node as soon as a new one is found; close the last
174 // one at the end
175 TextSegment sWord = xText.getTextAtIndex(0, nTextType);
176 TextSegment sBefore = xText.getTextBeforeIndex(0, nTextType);
177 TextSegment sBehind = xText.getTextBehindIndex(0, nTextType);
178 int nStart = 0;
179 for(int i = 1; i < nLength; i++)
181 TextSegment sTmp = xText.getTextAtIndex(i, nTextType);
182 TextSegment sTBef = xText.getTextBeforeIndex(i, nTextType);
183 TextSegment sTBeh = xText.getTextBehindIndex(i, nTextType);
184 if( ! ( sTmp.equals( sWord ) && sTBef.equals( sBefore ) &&
185 sTBeh.equals( sBehind ) ) )
187 aNode.addChild (new StringNode (textAtIndexNodeString(
188 nStart, i,
189 sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
190 sWord = sTmp;
191 sBefore = sTBef;
192 sBehind = sTBeh;
193 nStart = i;
196 // don't generate more than 50 children.
197 if (aNode.getChildCount() > 50)
199 sWord.SegmentText = "...";
200 break;
203 aNode.addChild (new StringNode (textAtIndexNodeString(
204 nStart, nLength,
205 sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
207 catch( IndexOutOfBoundsException e )
209 aNode.addChild (new StringNode (e.toString(), aNode));
211 catch (com.sun.star.lang.IllegalArgumentException e)
213 aNode.addChild (new StringNode (e.toString(), aNode));
217 return aNode;
222 /** getCharacter (display as array string) */
223 private String characters(XAccessibleText xText)
225 // get count (max. 30)
226 int nChars = xText.getCharacterCount();
227 if( nChars > 30 )
228 nChars = 30;
230 // build up string
231 StringBuffer aChars = new StringBuffer();
234 aChars.append( "[" );
235 for( int i = 0; i < nChars; i++)
237 aChars.append( xText.getCharacter(i) );
238 aChars.append( "," );
240 if( nChars > 0)
242 if( nChars == xText.getCharacterCount() )
243 aChars.deleteCharAt( aChars.length() - 1 );
244 else
245 aChars.append( "..." );
247 aChars.append( "]" );
249 catch( IndexOutOfBoundsException e )
251 aChars.append( " ERROR " );
254 // return result
255 return "getCharacters: " + aChars;
259 /** iterate over characters, and translate their positions
260 * back and forth */
261 private String bounds( XAccessibleText xText )
263 StringBuffer aBuffer = new StringBuffer( "bounds: " );
266 // iterate over characters
267 int nCount = xText.getCharacterCount();
268 for(int i = 0; i < nCount; i++ )
270 // get bounds for this character
271 Rectangle aRect = xText.getCharacterBounds( i );
273 // get the character by 'clicking' into the middle of
274 // the bounds
275 Point aMiddle = new Point();
276 aMiddle.X = aRect.X + (aRect.Width / 2) - 1;
277 aMiddle.Y = aRect.Y + (aRect.Height / 2 ) - 1;
278 int nIndex = xText.getIndexAtPoint( aMiddle );
280 // get the character, or a '#' for an illegal index
281 if( (nIndex >= 0) && (nIndex < xText.getCharacter(i)) )
282 aBuffer.append( xText.getCharacter(nIndex) );
283 else
284 aBuffer.append( '#' );
287 catch( IndexOutOfBoundsException e )
288 { ; } // ignore errors
290 return aBuffer.toString();
294 private AccessibleTreeNode getAttributes( XAccessibleText xText,
295 AccessibleTreeNode aParent)
297 String[] aAttributeList = new String[] {
298 "CharBackColor",
299 "CharColor",
300 "CharEscapement",
301 "CharHeight",
302 "CharPosture",
303 "CharStrikeout",
304 "CharUnderline",
305 "CharWeight",
306 "ParaAdjust",
307 "ParaBottomMargin",
308 "ParaFirstLineIndent",
309 "ParaLeftMargin",
310 "ParaLineSpacing",
311 "ParaRightMargin",
312 "ParaTabStops"};
314 AccessibleTreeNode aRet;
318 VectorNode aPortions = new VectorNode ("getAttributes", aParent);
320 int nIndex = 0;
321 int nLength = xText.getCharacterCount();
322 while( nIndex < nLength )
324 // get attribute run
325 String aPortion = null;
328 aPortion = xText.getTextAtIndex(
329 nIndex, AccessibleTextType.ATTRIBUTE_RUN).SegmentText;
331 catch(com.sun.star.lang.IllegalArgumentException e)
333 aPortion = new String ("");
336 // get attributes and make node with attribute children
337 PropertyValue[] aValues = xText.getCharacterAttributes(nIndex, aAttributeList);
338 VectorNode aAttrs = new VectorNode (aPortion, aPortions);
339 for( int i = 0; i < aValues.length; i++ )
341 new StringNode( aValues[i].Name + ": " + aValues[i].Value,
342 aAttrs );
345 // get next portion, but advance at least one
346 nIndex += (aPortion.length() > 0) ? aPortion.length() : 1;
349 aRet = aPortions;
351 catch( IndexOutOfBoundsException e )
353 aRet = new StringNode( "Exception caught:" + e, aParent );
356 return aRet;
360 static String[] aTextActions =
361 new String[] { "select...", "copy..." };
362 static String[] aEditableTextActions =
363 new String[] { "select...", "copy...",
364 "cut...", "paste...", "edit...", "format..." };
366 public String[] getActions (AccessibleTreeNode aNode)
368 XAccessibleEditableText xEText = null;
369 if (aNode instanceof AccTreeNode)
370 xEText = ((AccTreeNode)aNode).getEditText ();
372 return (xEText == null) ? aTextActions : aEditableTextActions;
375 public void performAction (AccessibleTreeNode aNode, int nIndex)
377 if ( ! (aNode instanceof AccTreeNode))
378 return;
380 AccTreeNode aATNode = (AccTreeNode)aNode;
381 TextActionDialog aDialog = null;
383 // create proper dialog
384 switch( nIndex )
386 case 0:
387 aDialog = new TextActionDialog( aATNode,
388 "Select range:",
389 "select" )
391 boolean action(
392 JTextComponent aText, AccTreeNode aNode )
393 throws IndexOutOfBoundsException
395 return aNode.getText().setSelection(
396 getSelectionStart(),
397 getSelectionEnd() );
400 break;
401 case 1:
402 aDialog = new TextActionDialog( aATNode,
403 "Select range and copy:",
404 "copy" )
406 boolean action(
407 JTextComponent aText, AccTreeNode aNode )
408 throws IndexOutOfBoundsException
410 return aNode.getText().copyText(
411 getSelectionStart(),
412 getSelectionEnd() );
415 break;
416 case 2:
417 aDialog = new TextActionDialog( aATNode,
418 "Select range and cut:",
419 "cut" )
421 boolean action(
422 JTextComponent aText, AccTreeNode aNode )
423 throws IndexOutOfBoundsException
425 return aNode.getEditText().cutText(
426 getSelectionStart(),
427 getSelectionEnd() );
430 break;
431 case 3:
432 aDialog = new TextActionDialog( aATNode,
433 "Place Caret and paste:",
434 "paste" )
436 boolean action(
437 JTextComponent aText, AccTreeNode aNode )
438 throws IndexOutOfBoundsException
440 return aNode.getEditText().pasteText(
441 aText.getCaretPosition() );
444 break;
445 case 4:
446 aDialog = new TextEditDialog( aATNode, "Edit text:",
447 "edit" );
448 break;
449 case 5:
450 aDialog = new TextAttributeDialog( aATNode );
451 break;
454 if( aDialog != null )
455 aDialog.show();
461 * Display a dialog with a text field and a pair of cancel/do-it buttons
463 class TextActionDialog extends JDialog
464 implements ActionListener
466 AccTreeNode aNode;
467 JTextArea aText;
468 String sName;
469 JCheckBox aIndexToggle;
471 public TextActionDialog( AccTreeNode aNd,
472 String sExplanation,
473 String sButtonText )
475 super( AccessibilityWorkBench.Instance() );
477 aNode = aNd;
478 sName = sButtonText;
479 init( sExplanation, aNode.getText().getText(), sButtonText );
480 // setSize( getPreferredSize() );
481 setSize( 350, 225 );
484 /** build dialog */
485 protected void init( String sExplanation,
486 String sText,
487 String sButtonText )
489 setTitle( sName );
491 // vertical stacking of the elements
492 Container aContent = getContentPane();
493 // aContent.setLayout( new BorderLayout() );
495 // label with explanation
496 if( sExplanation.length() > 0 )
497 aContent.add( new JLabel( sExplanation ), BorderLayout.NORTH );
499 // the text field
500 aText = new JTextArea();
501 aText.setText( sText );
502 aText.setColumns( Math.min( Math.max( 40, sText.length() ), 20 ) );
503 aText.setRows( sText.length() / 40 + 1 );
504 aText.setLineWrap( true );
505 aText.setEditable( false );
506 aContent.add( aText, BorderLayout.CENTER );
508 JPanel aButtons = new JPanel();
509 aButtons.setLayout( new FlowLayout() );
510 aIndexToggle = new JCheckBox( "reverse selection" );
511 aButtons.add( aIndexToggle );
512 JButton aActionButton = new JButton( sButtonText );
513 aActionButton.setActionCommand( "Action" );
514 aActionButton.addActionListener( this );
515 aButtons.add( aActionButton );
516 JButton aCancelButton = new JButton( "cancel" );
517 aCancelButton.setActionCommand( "Cancel" );
518 aCancelButton.addActionListener( this );
519 aButtons.add( aCancelButton );
521 // add Panel with buttons
522 aContent.add( aButtons, BorderLayout.SOUTH );
525 void cancel()
527 hide();
528 dispose();
531 void action()
533 String sError = null;
536 boolean bSuccess = action( aText, aNode );
537 if( !bSuccess )
538 sError = "Can't execute";
540 catch( IndexOutOfBoundsException e )
542 sError = "Index out of bounds";
545 if( sError != null )
546 JOptionPane.showMessageDialog( AccessibilityWorkBench.Instance(),
547 sError, sName,
548 JOptionPane.ERROR_MESSAGE);
550 cancel();
553 public void actionPerformed(ActionEvent e)
555 String sCommand = e.getActionCommand();
557 if( "Cancel".equals( sCommand ) )
558 cancel();
559 else if( "Action".equals( sCommand ) )
560 action();
564 int getSelectionStart() { return getSelection(true); }
565 int getSelectionEnd() { return getSelection(false); }
566 int getSelection(boolean bStart)
568 return ( bStart ^ aIndexToggle.isSelected() )
569 ? aText.getSelectionStart() : aText.getSelectionEnd();
574 /** override this for dialog-specific action */
575 boolean action( JTextComponent aText, AccTreeNode aNode )
576 throws IndexOutOfBoundsException
578 return false;
583 class TextEditDialog extends TextActionDialog
585 public TextEditDialog( AccTreeNode aNode,
586 String sExplanation,
587 String sButtonText )
589 super( aNode, sExplanation, sButtonText );
592 protected void init( String sExplanation,
593 String sText,
594 String sButtonText )
596 super.init( sExplanation, sText, sButtonText );
597 aText.setEditable( true );
601 /** edit the text */
602 boolean action( JTextComponent aText, AccTreeNode aNode )
604 // is this text editable? if not, fudge you and return
605 XAccessibleEditableText xEdit = aNode.getEditText();
606 return ( xEdit == null ) ? false :
607 updateText( xEdit, aText.getText() );
611 /** update the text */
612 boolean updateText( XAccessibleEditableText xEdit, String sNew )
614 String sOld = xEdit.getText();
616 // false alarm? Early out if no change was done!
617 if( sOld.equals( sNew ) )
618 return false;
620 // get the minimum length of both strings
621 int nMinLength = sOld.length();
622 if( sNew.length() < nMinLength )
623 nMinLength = sNew.length();
625 // count equal characters from front and end
626 int nFront = 0;
627 while( (nFront < nMinLength) &&
628 (sNew.charAt(nFront) == sOld.charAt(nFront)) )
629 nFront++;
630 int nBack = 0;
631 while( (nBack < nMinLength) &&
632 ( sNew.charAt(sNew.length()-nBack-1) ==
633 sOld.charAt(sOld.length()-nBack-1) ) )
634 nBack++;
635 if( nFront + nBack > nMinLength )
636 nBack = nMinLength - nFront;
638 // so... the first nFront and the last nBack characters
639 // are the same. Change the others!
640 String sDel = sOld.substring( nFront, sOld.length() - nBack );
641 String sIns = sNew.substring( nFront, sNew.length() - nBack );
643 System.out.println("edit text: " +
644 sOld.substring(0, nFront) +
645 " [ " + sDel + " -> " + sIns + " ] " +
646 sOld.substring(sOld.length() - nBack) );
648 boolean bRet = false;
651 // edit the text, and use
652 // (set|insert|delete|replace)Text as needed
653 if( nFront+nBack == 0 )
654 bRet = xEdit.setText( sIns );
655 else if( sDel.length() == 0 )
656 bRet = xEdit.insertText( sIns, nFront );
657 else if( sIns.length() == 0 )
658 bRet = xEdit.deleteText( nFront, sOld.length()-nBack );
659 else
660 bRet = xEdit.replaceText(nFront, sOld.length()-nBack,sIns);
662 catch( IndexOutOfBoundsException e )
664 bRet = false;
667 return bRet;
672 class TextAttributeDialog extends TextActionDialog
674 public TextAttributeDialog(
675 AccTreeNode aNode )
677 super( aNode, "Choose attributes, select text, and press 'Set':",
678 "set" );
681 private JCheckBox aBold, aUnderline, aItalics;
682 private Color aForeground, aBackground;
684 protected void init( String sExplanation,
685 String sText,
686 String sButtonText )
688 super.init( sExplanation, sText, sButtonText );
690 aForeground = Color.black;
691 aBackground = Color.white;
693 JPanel aAttr = new JPanel();
694 aAttr.setLayout( new BoxLayout( aAttr, BoxLayout.Y_AXIS ) );
696 aBold = new JCheckBox( "bold" );
697 aUnderline = new JCheckBox( "underline" );
698 aItalics = new JCheckBox( "italics" );
700 JButton aForeButton = new JButton("Foreground", new ColorIcon(true));
701 aForeButton.addActionListener( new ActionListener() {
702 public void actionPerformed(ActionEvent e)
704 aForeground = JColorChooser.showDialog(
705 TextAttributeDialog.this,
706 "Select Foreground Color",
707 aForeground);
709 } );
711 JButton aBackButton = new JButton("Background", new ColorIcon(false));
712 aBackButton.addActionListener( new ActionListener() {
713 public void actionPerformed(ActionEvent e)
715 aBackground = JColorChooser.showDialog(
716 TextAttributeDialog.this,
717 "Select Background Color",
718 aBackground);
720 } );
722 aAttr.add( aBold );
723 aAttr.add( aUnderline );
724 aAttr.add( aItalics );
725 aAttr.add( aForeButton );
726 aAttr.add( aBackButton );
728 getContentPane().add( aAttr, BorderLayout.WEST );
732 class ColorIcon implements Icon
734 boolean bForeground;
735 static final int nHeight = 16;
736 static final int nWidth = 16;
738 public ColorIcon(boolean bWhich) { bForeground = bWhich; }
739 public int getIconHeight() { return nHeight; }
740 public int getIconWidth() { return nWidth; }
741 public void paintIcon(Component c, Graphics g, int x, int y)
743 g.setColor( getColor() );
744 g.fillRect( x, y, nHeight, nWidth );
745 g.setColor( c.getForeground() );
746 g.drawRect( x, y, nHeight, nWidth );
748 Color getColor()
750 return bForeground ? aForeground : aBackground;
756 /** edit the text */
757 boolean action( JTextComponent aText, AccTreeNode aNode )
758 throws IndexOutOfBoundsException
760 // is this text editable? if not, fudge you and return
761 XAccessibleEditableText xEdit = aNode.getEditText();
762 boolean bSuccess = false;
763 if( xEdit != null )
765 PropertyValue[] aSequence = new PropertyValue[6];
766 aSequence[0] = new PropertyValue();
767 aSequence[0].Name = "CharWeight";
768 aSequence[0].Value = new Integer( aBold.isSelected() ? 150 : 100 );
769 aSequence[1] = new PropertyValue();
770 aSequence[1].Name = "CharUnderline";
771 aSequence[1].Value = new Integer( aUnderline.isSelected() ? 1 : 0 );
772 aSequence[2] = new PropertyValue();
773 aSequence[2].Name = "CharBackColor";
774 aSequence[2].Value = new Integer( aBackground.getRGB() );
775 aSequence[3] = new PropertyValue();
776 aSequence[3].Name = "CharColor";
777 aSequence[3].Value = new Integer( aForeground.getRGB() );
778 aSequence[4] = new PropertyValue();
779 aSequence[4].Name = "CharPosture";
780 aSequence[4].Value = new Integer( aItalics.isSelected() ? 1 : 0 );
781 aSequence[5] = new PropertyValue();
782 aSequence[5].Name = "CharBackTransparent";
783 aSequence[5].Value = new Boolean( false );
785 bSuccess = xEdit.setAttributes( getSelectionStart(),
786 getSelectionEnd(),
787 aSequence );
789 return bSuccess;