bump product version to 4.2.0.1
[LibreOffice.git] / toolkit / test / accessibility / AccessibleTextHandler.java
blob46b4b69d4b4c168927abb521aba3c5e3f30f0633
1 /*
2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 import com.sun.star.accessibility.AccessibleTextType;
20 import com.sun.star.accessibility.TextSegment;
21 import com.sun.star.accessibility.XAccessibleContext;
22 import com.sun.star.accessibility.XAccessibleText;
23 import com.sun.star.accessibility.XAccessibleEditableText;
25 import com.sun.star.awt.Rectangle;
26 import com.sun.star.awt.Point;
27 import com.sun.star.uno.UnoRuntime;
28 import com.sun.star.lang.IndexOutOfBoundsException;
29 import com.sun.star.beans.PropertyValue;
31 import java.awt.Container;
32 import java.awt.FlowLayout;
33 import java.awt.BorderLayout;
34 import java.awt.Color;
35 import java.awt.Component;
36 import java.awt.Graphics;
37 import java.awt.event.ActionListener;
38 import java.awt.event.ActionEvent;
39 import javax.swing.JDialog;
40 import javax.swing.JButton;
41 import javax.swing.JPanel;
42 import javax.swing.JLabel;
43 import javax.swing.Icon;
44 import javax.swing.JTextArea;
45 import javax.swing.JOptionPane;
46 import javax.swing.JCheckBox;
47 import javax.swing.JColorChooser;
48 import javax.swing.BoxLayout;
49 import javax.swing.text.JTextComponent;
52 class AccessibleTextHandler extends NodeHandler
54 public NodeHandler createHandler (XAccessibleContext xContext)
56 XAccessibleText xText = UnoRuntime.queryInterface (
57 XAccessibleText.class, xContext);
58 if (xText != null)
59 return new AccessibleTextHandler (xText);
60 else
61 return null;
64 public AccessibleTextHandler ()
68 public AccessibleTextHandler (XAccessibleText xText)
70 if (xText != null)
71 maChildList.setSize (8);
74 public AccessibleTreeNode createChild (AccessibleTreeNode aParent, int nIndex)
76 AccessibleTreeNode aChild = null;
77 XAccessibleText xText = null;
78 if (aParent instanceof AccTreeNode)
79 xText = ((AccTreeNode)aParent).getText();
81 try
83 if( xText != null )
85 switch( nIndex )
87 case 0:
88 aChild = new StringNode (xText.getText(), aParent);
89 break;
90 case 1:
91 aChild = new StringNode ("# chars: " + xText.getCharacterCount(), aParent);
92 break;
93 case 2:
94 aChild = new StringNode (characters( xText ), aParent);
95 break;
96 case 3:
97 aChild = new StringNode ("selection: "
98 + "[" + xText.getSelectionStart()
99 + "," + xText.getSelectionEnd()
100 + "] \"" + xText.getSelectedText() + "\"",
101 aParent);
102 break;
103 case 4:
104 aChild = new StringNode ("getCaretPosition: " + xText.getCaretPosition(), aParent);
105 break;
106 case 5:
108 VectorNode aVec = new VectorNode("portions", aParent);
109 aChild = aVec;
110 aVec.addChild(
111 textAtIndexNode( xText, "Character",
112 AccessibleTextType.CHARACTER,
113 aParent ) );
114 aVec.addChild(
115 textAtIndexNode( xText, "Word",
116 AccessibleTextType.WORD,
117 aParent ) );
118 aVec.addChild(
119 textAtIndexNode( xText, "Sentence",
120 AccessibleTextType.SENTENCE,
121 aParent ) );
122 aVec.addChild(
123 textAtIndexNode( xText, "Paragraph",
124 AccessibleTextType.PARAGRAPH,
125 aParent ) );
126 aVec.addChild(
127 textAtIndexNode( xText, "Line",
128 AccessibleTextType.LINE,
129 aParent ) );
130 aVec.addChild(
131 textAtIndexNode( xText, "Attribute",
132 AccessibleTextType.ATTRIBUTE_RUN,
133 aParent ) );
134 aVec.addChild(
135 textAtIndexNode( xText, "Glyph",
136 AccessibleTextType.GLYPH,
137 aParent ) );
139 break;
140 case 6:
141 aChild = new StringNode (bounds( xText ), aParent);
142 break;
143 case 7:
144 aChild = getAttributes( xText, aParent );
145 break;
146 default:
147 aChild = new StringNode ("unknown child index " + nIndex, aParent);
151 catch (Exception e)
153 // Return empty child.
156 return aChild;
160 private String textAtIndexNodeString(
161 int nStart, int nEnd,
162 String sWord, String sBefore, String sBehind)
164 return "[" + nStart + "," + nEnd + "] "
165 + "\"" + sWord + "\" \t"
166 + "(" + sBefore + ","
167 + "" + sBehind + ")";
170 /** Create a text node that lists all strings of a particular text type
172 private AccessibleTreeNode textAtIndexNode(
173 XAccessibleText xText,
174 String sName,
175 short nTextType,
176 AccessibleTreeNode aParent)
178 VectorNode aNode = new VectorNode (sName, aParent);
180 // get word at all positions;
181 // for nicer display, compare current word to previous one and
182 // make a new node for every interval, not for every word
183 int nLength = xText.getCharacterCount();
184 if( nLength > 0 )
188 // sWord + nStart mark the current word
189 // make a node as soon as a new one is found; close the last
190 // one at the end
191 TextSegment sWord = xText.getTextAtIndex(0, nTextType);
192 TextSegment sBefore = xText.getTextBeforeIndex(0, nTextType);
193 TextSegment sBehind = xText.getTextBehindIndex(0, nTextType);
194 int nStart = 0;
195 for(int i = 1; i < nLength; i++)
197 TextSegment sTmp = xText.getTextAtIndex(i, nTextType);
198 TextSegment sTBef = xText.getTextBeforeIndex(i, nTextType);
199 TextSegment sTBeh = xText.getTextBehindIndex(i, nTextType);
200 if( ! ( sTmp.equals( sWord ) && sTBef.equals( sBefore ) &&
201 sTBeh.equals( sBehind ) ) )
203 aNode.addChild (new StringNode (textAtIndexNodeString(
204 nStart, i,
205 sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
206 sWord = sTmp;
207 sBefore = sTBef;
208 sBehind = sTBeh;
209 nStart = i;
212 // don't generate more than 50 children.
213 if (aNode.getChildCount() > 50)
215 sWord.SegmentText = "...";
216 break;
219 aNode.addChild (new StringNode (textAtIndexNodeString(
220 nStart, nLength,
221 sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
223 catch( IndexOutOfBoundsException e )
225 aNode.addChild (new StringNode (e.toString(), aNode));
227 catch (com.sun.star.lang.IllegalArgumentException e)
229 aNode.addChild (new StringNode (e.toString(), aNode));
233 return aNode;
238 /** getCharacter (display as array string) */
239 private String characters(XAccessibleText xText)
241 // get count (max. 30)
242 int nChars = xText.getCharacterCount();
243 if( nChars > 30 )
244 nChars = 30;
246 // build up string
247 StringBuffer aChars = new StringBuffer();
250 aChars.append( "[" );
251 for( int i = 0; i < nChars; i++)
253 aChars.append( xText.getCharacter(i) );
254 aChars.append( "," );
256 if( nChars > 0)
258 if( nChars == xText.getCharacterCount() )
259 aChars.deleteCharAt( aChars.length() - 1 );
260 else
261 aChars.append( "..." );
263 aChars.append( "]" );
265 catch( IndexOutOfBoundsException e )
267 aChars.append( " ERROR " );
270 // return result
271 return "getCharacters: " + aChars;
275 /** iterate over characters, and translate their positions
276 * back and forth */
277 private String bounds( XAccessibleText xText )
279 StringBuffer aBuffer = new StringBuffer( "bounds: " );
282 // iterate over characters
283 int nCount = xText.getCharacterCount();
284 for(int i = 0; i < nCount; i++ )
286 // get bounds for this character
287 Rectangle aRect = xText.getCharacterBounds( i );
289 // get the character by 'clicking' into the middle of
290 // the bounds
291 Point aMiddle = new Point();
292 aMiddle.X = aRect.X + (aRect.Width / 2) - 1;
293 aMiddle.Y = aRect.Y + (aRect.Height / 2 ) - 1;
294 int nIndex = xText.getIndexAtPoint( aMiddle );
296 // get the character, or a '#' for an illegal index
297 if( (nIndex >= 0) && (nIndex < xText.getCharacter(i)) )
298 aBuffer.append( xText.getCharacter(nIndex) );
299 else
300 aBuffer.append( '#' );
303 catch( IndexOutOfBoundsException e )
304 { ; } // ignore errors
306 return aBuffer.toString();
310 private AccessibleTreeNode getAttributes( XAccessibleText xText,
311 AccessibleTreeNode aParent)
313 String[] aAttributeList = new String[] {
314 "CharBackColor",
315 "CharColor",
316 "CharEscapement",
317 "CharHeight",
318 "CharPosture",
319 "CharStrikeout",
320 "CharUnderline",
321 "CharWeight",
322 "ParaAdjust",
323 "ParaBottomMargin",
324 "ParaFirstLineIndent",
325 "ParaLeftMargin",
326 "ParaLineSpacing",
327 "ParaRightMargin",
328 "ParaTabStops"};
330 AccessibleTreeNode aRet;
334 VectorNode aPortions = new VectorNode ("getAttributes", aParent);
336 int nIndex = 0;
337 int nLength = xText.getCharacterCount();
338 while( nIndex < nLength )
340 // get attribute run
341 String aPortion = null;
344 aPortion = xText.getTextAtIndex(
345 nIndex, AccessibleTextType.ATTRIBUTE_RUN).SegmentText;
347 catch(com.sun.star.lang.IllegalArgumentException e)
349 aPortion = new String ("");
352 // get attributes and make node with attribute children
353 PropertyValue[] aValues = xText.getCharacterAttributes(nIndex, aAttributeList);
354 VectorNode aAttrs = new VectorNode (aPortion, aPortions);
355 for( int i = 0; i < aValues.length; i++ )
357 new StringNode( aValues[i].Name + ": " + aValues[i].Value,
358 aAttrs );
361 // get next portion, but advance at least one
362 nIndex += (aPortion.length() > 0) ? aPortion.length() : 1;
365 aRet = aPortions;
367 catch( IndexOutOfBoundsException e )
369 aRet = new StringNode( "Exception caught:" + e, aParent );
372 return aRet;
376 static String[] aTextActions =
377 new String[] { "select...", "copy..." };
378 static String[] aEditableTextActions =
379 new String[] { "select...", "copy...",
380 "cut...", "paste...", "edit...", "format..." };
382 public String[] getActions (AccessibleTreeNode aNode)
384 XAccessibleEditableText xEText = null;
385 if (aNode instanceof AccTreeNode)
386 xEText = ((AccTreeNode)aNode).getEditText ();
388 return (xEText == null) ? aTextActions : aEditableTextActions;
391 public void performAction (AccessibleTreeNode aNode, int nIndex)
393 if ( ! (aNode instanceof AccTreeNode))
394 return;
396 AccTreeNode aATNode = (AccTreeNode)aNode;
397 TextActionDialog aDialog = null;
399 // create proper dialog
400 switch( nIndex )
402 case 0:
403 aDialog = new TextActionDialog( aATNode,
404 "Select range:",
405 "select" )
407 boolean action(
408 JTextComponent aText, AccTreeNode aNode )
409 throws IndexOutOfBoundsException
411 return aNode.getText().setSelection(
412 getSelectionStart(),
413 getSelectionEnd() );
416 break;
417 case 1:
418 aDialog = new TextActionDialog( aATNode,
419 "Select range and copy:",
420 "copy" )
422 boolean action(
423 JTextComponent aText, AccTreeNode aNode )
424 throws IndexOutOfBoundsException
426 return aNode.getText().copyText(
427 getSelectionStart(),
428 getSelectionEnd() );
431 break;
432 case 2:
433 aDialog = new TextActionDialog( aATNode,
434 "Select range and cut:",
435 "cut" )
437 boolean action(
438 JTextComponent aText, AccTreeNode aNode )
439 throws IndexOutOfBoundsException
441 return aNode.getEditText().cutText(
442 getSelectionStart(),
443 getSelectionEnd() );
446 break;
447 case 3:
448 aDialog = new TextActionDialog( aATNode,
449 "Place Caret and paste:",
450 "paste" )
452 boolean action(
453 JTextComponent aText, AccTreeNode aNode )
454 throws IndexOutOfBoundsException
456 return aNode.getEditText().pasteText(
457 aText.getCaretPosition() );
460 break;
461 case 4:
462 aDialog = new TextEditDialog( aATNode, "Edit text:",
463 "edit" );
464 break;
465 case 5:
466 aDialog = new TextAttributeDialog( aATNode );
467 break;
470 if( aDialog != null )
471 aDialog.show();
477 * Display a dialog with a text field and a pair of cancel/do-it buttons
479 class TextActionDialog extends JDialog
480 implements ActionListener
482 AccTreeNode aNode;
483 JTextArea aText;
484 String sName;
485 JCheckBox aIndexToggle;
487 public TextActionDialog( AccTreeNode aNd,
488 String sExplanation,
489 String sButtonText )
491 super( AccessibilityWorkBench.Instance() );
493 aNode = aNd;
494 sName = sButtonText;
495 init( sExplanation, aNode.getText().getText(), sButtonText );
496 // setSize( getPreferredSize() );
497 setSize( 350, 225 );
500 /** build dialog */
501 protected void init( String sExplanation,
502 String sText,
503 String sButtonText )
505 setTitle( sName );
507 // vertical stacking of the elements
508 Container aContent = getContentPane();
509 // aContent.setLayout( new BorderLayout() );
511 // label with explanation
512 if( sExplanation.length() > 0 )
513 aContent.add( new JLabel( sExplanation ), BorderLayout.NORTH );
515 // the text field
516 aText = new JTextArea();
517 aText.setText( sText );
518 aText.setColumns( Math.min( Math.max( 40, sText.length() ), 20 ) );
519 aText.setRows( sText.length() / 40 + 1 );
520 aText.setLineWrap( true );
521 aText.setEditable( false );
522 aContent.add( aText, BorderLayout.CENTER );
524 JPanel aButtons = new JPanel();
525 aButtons.setLayout( new FlowLayout() );
526 aIndexToggle = new JCheckBox( "reverse selection" );
527 aButtons.add( aIndexToggle );
528 JButton aActionButton = new JButton( sButtonText );
529 aActionButton.setActionCommand( "Action" );
530 aActionButton.addActionListener( this );
531 aButtons.add( aActionButton );
532 JButton aCancelButton = new JButton( "cancel" );
533 aCancelButton.setActionCommand( "Cancel" );
534 aCancelButton.addActionListener( this );
535 aButtons.add( aCancelButton );
537 // add Panel with buttons
538 aContent.add( aButtons, BorderLayout.SOUTH );
541 void cancel()
543 hide();
544 dispose();
547 void action()
549 String sError = null;
552 boolean bSuccess = action( aText, aNode );
553 if( !bSuccess )
554 sError = "Can't execute";
556 catch( IndexOutOfBoundsException e )
558 sError = "Index out of bounds";
561 if( sError != null )
562 JOptionPane.showMessageDialog( AccessibilityWorkBench.Instance(),
563 sError, sName,
564 JOptionPane.ERROR_MESSAGE);
566 cancel();
569 public void actionPerformed(ActionEvent e)
571 String sCommand = e.getActionCommand();
573 if( "Cancel".equals( sCommand ) )
574 cancel();
575 else if( "Action".equals( sCommand ) )
576 action();
580 int getSelectionStart() { return getSelection(true); }
581 int getSelectionEnd() { return getSelection(false); }
582 int getSelection(boolean bStart)
584 return ( bStart ^ aIndexToggle.isSelected() )
585 ? aText.getSelectionStart() : aText.getSelectionEnd();
590 /** override this for dialog-specific action */
591 boolean action( JTextComponent aText, AccTreeNode aNode )
592 throws IndexOutOfBoundsException
594 return false;
599 class TextEditDialog extends TextActionDialog
601 public TextEditDialog( AccTreeNode aNode,
602 String sExplanation,
603 String sButtonText )
605 super( aNode, sExplanation, sButtonText );
608 protected void init( String sExplanation,
609 String sText,
610 String sButtonText )
612 super.init( sExplanation, sText, sButtonText );
613 aText.setEditable( true );
617 /** edit the text */
618 boolean action( JTextComponent aText, AccTreeNode aNode )
620 // is this text editable? if not, fudge you and return
621 XAccessibleEditableText xEdit = aNode.getEditText();
622 return ( xEdit == null ) ? false :
623 updateText( xEdit, aText.getText() );
627 /** update the text */
628 boolean updateText( XAccessibleEditableText xEdit, String sNew )
630 String sOld = xEdit.getText();
632 // false alarm? Early out if no change was done!
633 if( sOld.equals( sNew ) )
634 return false;
636 // get the minimum length of both strings
637 int nMinLength = sOld.length();
638 if( sNew.length() < nMinLength )
639 nMinLength = sNew.length();
641 // count equal characters from front and end
642 int nFront = 0;
643 while( (nFront < nMinLength) &&
644 (sNew.charAt(nFront) == sOld.charAt(nFront)) )
645 nFront++;
646 int nBack = 0;
647 while( (nBack < nMinLength) &&
648 ( sNew.charAt(sNew.length()-nBack-1) ==
649 sOld.charAt(sOld.length()-nBack-1) ) )
650 nBack++;
651 if( nFront + nBack > nMinLength )
652 nBack = nMinLength - nFront;
654 // so... the first nFront and the last nBack characters
655 // are the same. Change the others!
656 String sDel = sOld.substring( nFront, sOld.length() - nBack );
657 String sIns = sNew.substring( nFront, sNew.length() - nBack );
659 System.out.println("edit text: " +
660 sOld.substring(0, nFront) +
661 " [ " + sDel + " -> " + sIns + " ] " +
662 sOld.substring(sOld.length() - nBack) );
664 boolean bRet = false;
667 // edit the text, and use
668 // (set|insert|delete|replace)Text as needed
669 if( nFront+nBack == 0 )
670 bRet = xEdit.setText( sIns );
671 else if( sDel.length() == 0 )
672 bRet = xEdit.insertText( sIns, nFront );
673 else if( sIns.length() == 0 )
674 bRet = xEdit.deleteText( nFront, sOld.length()-nBack );
675 else
676 bRet = xEdit.replaceText(nFront, sOld.length()-nBack,sIns);
678 catch( IndexOutOfBoundsException e )
680 bRet = false;
683 return bRet;
688 class TextAttributeDialog extends TextActionDialog
690 public TextAttributeDialog(
691 AccTreeNode aNode )
693 super( aNode, "Choose attributes, select text, and press 'Set':",
694 "set" );
697 private JCheckBox aBold, aUnderline, aItalics;
698 private Color aForeground, aBackground;
700 protected void init( String sExplanation,
701 String sText,
702 String sButtonText )
704 super.init( sExplanation, sText, sButtonText );
706 aForeground = Color.black;
707 aBackground = Color.white;
709 JPanel aAttr = new JPanel();
710 aAttr.setLayout( new BoxLayout( aAttr, BoxLayout.Y_AXIS ) );
712 aBold = new JCheckBox( "bold" );
713 aUnderline = new JCheckBox( "underline" );
714 aItalics = new JCheckBox( "italics" );
716 JButton aForeButton = new JButton("Foreground", new ColorIcon(true));
717 aForeButton.addActionListener( new ActionListener() {
718 public void actionPerformed(ActionEvent e)
720 aForeground = JColorChooser.showDialog(
721 TextAttributeDialog.this,
722 "Select Foreground Color",
723 aForeground);
725 } );
727 JButton aBackButton = new JButton("Background", new ColorIcon(false));
728 aBackButton.addActionListener( new ActionListener() {
729 public void actionPerformed(ActionEvent e)
731 aBackground = JColorChooser.showDialog(
732 TextAttributeDialog.this,
733 "Select Background Color",
734 aBackground);
736 } );
738 aAttr.add( aBold );
739 aAttr.add( aUnderline );
740 aAttr.add( aItalics );
741 aAttr.add( aForeButton );
742 aAttr.add( aBackButton );
744 getContentPane().add( aAttr, BorderLayout.WEST );
748 class ColorIcon implements Icon
750 boolean bForeground;
751 static final int nHeight = 16;
752 static final int nWidth = 16;
754 public ColorIcon(boolean bWhich) { bForeground = bWhich; }
755 public int getIconHeight() { return nHeight; }
756 public int getIconWidth() { return nWidth; }
757 public void paintIcon(Component c, Graphics g, int x, int y)
759 g.setColor( getColor() );
760 g.fillRect( x, y, nHeight, nWidth );
761 g.setColor( c.getForeground() );
762 g.drawRect( x, y, nHeight, nWidth );
764 Color getColor()
766 return bForeground ? aForeground : aBackground;
772 /** edit the text */
773 boolean action( JTextComponent aText, AccTreeNode aNode )
774 throws IndexOutOfBoundsException
776 // is this text editable? if not, fudge you and return
777 XAccessibleEditableText xEdit = aNode.getEditText();
778 boolean bSuccess = false;
779 if( xEdit != null )
781 PropertyValue[] aSequence = new PropertyValue[6];
782 aSequence[0] = new PropertyValue();
783 aSequence[0].Name = "CharWeight";
784 aSequence[0].Value = new Integer( aBold.isSelected() ? 150 : 100 );
785 aSequence[1] = new PropertyValue();
786 aSequence[1].Name = "CharUnderline";
787 aSequence[1].Value = new Integer( aUnderline.isSelected() ? 1 : 0 );
788 aSequence[2] = new PropertyValue();
789 aSequence[2].Name = "CharBackColor";
790 aSequence[2].Value = new Integer( aBackground.getRGB() );
791 aSequence[3] = new PropertyValue();
792 aSequence[3].Name = "CharColor";
793 aSequence[3].Value = new Integer( aForeground.getRGB() );
794 aSequence[4] = new PropertyValue();
795 aSequence[4].Name = "CharPosture";
796 aSequence[4].Value = new Integer( aItalics.isSelected() ? 1 : 0 );
797 aSequence[5] = new PropertyValue();
798 aSequence[5].Name = "CharBackTransparent";
799 aSequence[5].Value = new Boolean( false );
801 bSuccess = xEdit.setAttributes( getSelectionStart(),
802 getSelectionEnd(),
803 aSequence );
805 return bSuccess;