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 java
.awt
.BorderLayout
;
20 import java
.awt
.Color
;
21 import java
.awt
.Component
;
22 import java
.awt
.Container
;
23 import java
.awt
.FlowLayout
;
24 import java
.awt
.Graphics
;
25 import java
.awt
.event
.ActionEvent
;
26 import java
.awt
.event
.ActionListener
;
28 import javax
.swing
.BoxLayout
;
29 import javax
.swing
.Icon
;
30 import javax
.swing
.JButton
;
31 import javax
.swing
.JCheckBox
;
32 import javax
.swing
.JColorChooser
;
33 import javax
.swing
.JDialog
;
34 import javax
.swing
.JLabel
;
35 import javax
.swing
.JOptionPane
;
36 import javax
.swing
.JPanel
;
37 import javax
.swing
.JTextArea
;
38 import javax
.swing
.text
.JTextComponent
;
40 import com
.sun
.star
.accessibility
.AccessibleTextType
;
41 import com
.sun
.star
.accessibility
.TextSegment
;
42 import com
.sun
.star
.accessibility
.XAccessibleContext
;
43 import com
.sun
.star
.accessibility
.XAccessibleEditableText
;
44 import com
.sun
.star
.accessibility
.XAccessibleText
;
45 import com
.sun
.star
.awt
.Point
;
46 import com
.sun
.star
.awt
.Rectangle
;
47 import com
.sun
.star
.beans
.PropertyValue
;
48 import com
.sun
.star
.beans
.UnknownPropertyException
;
49 import com
.sun
.star
.lang
.IndexOutOfBoundsException
;
50 import com
.sun
.star
.uno
.UnoRuntime
;
53 class AccessibleTextHandler
extends NodeHandler
56 public NodeHandler
createHandler (XAccessibleContext xContext
)
58 XAccessibleText xText
= UnoRuntime
.queryInterface (
59 XAccessibleText
.class, xContext
);
61 return new AccessibleTextHandler (xText
);
66 public AccessibleTextHandler ()
70 private AccessibleTextHandler (XAccessibleText xText
)
73 maChildList
.setSize (8);
77 public AccessibleTreeNode
createChild (AccessibleTreeNode aParent
, int nIndex
)
79 AccessibleTreeNode aChild
= null;
80 XAccessibleText xText
= null;
81 if (aParent
instanceof AccTreeNode
)
82 xText
= ((AccTreeNode
)aParent
).getText();
91 aChild
= new StringNode (xText
.getText(), aParent
);
94 aChild
= new StringNode ("# chars: " + xText
.getCharacterCount(), aParent
);
97 aChild
= new StringNode (characters( xText
), aParent
);
100 aChild
= new StringNode ("selection: "
101 + "[" + xText
.getSelectionStart()
102 + "," + xText
.getSelectionEnd()
103 + "] \"" + xText
.getSelectedText() + "\"",
107 aChild
= new StringNode ("getCaretPosition: " + xText
.getCaretPosition(), aParent
);
111 VectorNode aVec
= new VectorNode("portions", aParent
);
114 textAtIndexNode( xText
, "Character",
115 AccessibleTextType
.CHARACTER
,
118 textAtIndexNode( xText
, "Word",
119 AccessibleTextType
.WORD
,
122 textAtIndexNode( xText
, "Sentence",
123 AccessibleTextType
.SENTENCE
,
126 textAtIndexNode( xText
, "Paragraph",
127 AccessibleTextType
.PARAGRAPH
,
130 textAtIndexNode( xText
, "Line",
131 AccessibleTextType
.LINE
,
134 textAtIndexNode( xText
, "Attribute",
135 AccessibleTextType
.ATTRIBUTE_RUN
,
138 textAtIndexNode( xText
, "Glyph",
139 AccessibleTextType
.GLYPH
,
144 aChild
= new StringNode (bounds( xText
), aParent
);
147 aChild
= getAttributes( xText
, aParent
);
150 aChild
= new StringNode ("unknown child index " + nIndex
, aParent
);
156 // Return empty child.
163 private String
textAtIndexNodeString(
164 int nStart
, int nEnd
,
165 String sWord
, String sBefore
, String sBehind
)
167 return "[" + nStart
+ "," + nEnd
+ "] "
168 + "\"" + sWord
+ "\" \t"
169 + "(" + sBefore
+ "," + sBehind
+ ")";
172 /** Create a text node that lists all strings of a particular text type
174 private AccessibleTreeNode
textAtIndexNode(
175 XAccessibleText xText
,
178 AccessibleTreeNode aParent
)
180 VectorNode aNode
= new VectorNode (sName
, aParent
);
182 // get word at all positions;
183 // for nicer display, compare current word to previous one and
184 // make a new node for every interval, not for every word
185 int nLength
= xText
.getCharacterCount();
190 // sWord + nStart mark the current word
191 // make a node as soon as a new one is found; close the last
193 TextSegment sWord
= xText
.getTextAtIndex(0, nTextType
);
194 TextSegment sBefore
= xText
.getTextBeforeIndex(0, nTextType
);
195 TextSegment sBehind
= xText
.getTextBehindIndex(0, nTextType
);
197 for(int i
= 1; i
< nLength
; i
++)
199 TextSegment sTmp
= xText
.getTextAtIndex(i
, nTextType
);
200 TextSegment sTBef
= xText
.getTextBeforeIndex(i
, nTextType
);
201 TextSegment sTBeh
= xText
.getTextBehindIndex(i
, nTextType
);
202 if( ! ( sTmp
.equals( sWord
) && sTBef
.equals( sBefore
) &&
203 sTBeh
.equals( sBehind
) ) )
205 aNode
.addChild (new StringNode (textAtIndexNodeString(
207 sWord
.SegmentText
, sBefore
.SegmentText
, sBehind
.SegmentText
), aNode
));
214 // don't generate more than 50 children.
215 if (aNode
.getChildCount() > 50)
217 sWord
.SegmentText
= "...";
221 aNode
.addChild (new StringNode (textAtIndexNodeString(
223 sWord
.SegmentText
, sBefore
.SegmentText
, sBehind
.SegmentText
), aNode
));
225 catch( IndexOutOfBoundsException e
)
227 aNode
.addChild (new StringNode (e
.toString(), aNode
));
229 catch (com
.sun
.star
.lang
.IllegalArgumentException e
)
231 aNode
.addChild (new StringNode (e
.toString(), aNode
));
240 /** getCharacter (display as array string) */
241 private String
characters(XAccessibleText xText
)
243 // get count (max. 30)
244 int nChars
= xText
.getCharacterCount();
249 StringBuffer aChars
= new StringBuffer();
252 aChars
.append( '[' );
253 for( int i
= 0; i
< nChars
; i
++)
255 aChars
.append( xText
.getCharacter(i
) );
256 aChars
.append( ',' );
260 if( nChars
== xText
.getCharacterCount() )
261 aChars
.deleteCharAt( aChars
.length() - 1 );
263 aChars
.append( "..." );
265 aChars
.append( ']' );
267 catch( IndexOutOfBoundsException e
)
269 aChars
.append( " ERROR " );
273 return "getCharacters: " + aChars
;
277 /** iterate over characters, and translate their positions
279 private String
bounds( XAccessibleText xText
)
281 StringBuffer aBuffer
= new StringBuffer( "bounds: " );
284 // iterate over characters
285 int nCount
= xText
.getCharacterCount();
286 for(int i
= 0; i
< nCount
; i
++ )
288 // get bounds for this character
289 Rectangle aRect
= xText
.getCharacterBounds( i
);
291 // get the character by 'clicking' into the middle of
293 Point aMiddle
= new Point();
294 aMiddle
.X
= aRect
.X
+ (aRect
.Width
/ 2) - 1;
295 aMiddle
.Y
= aRect
.Y
+ (aRect
.Height
/ 2 ) - 1;
296 int nIndex
= xText
.getIndexAtPoint( aMiddle
);
298 // get the character, or a '#' for an illegal index
299 if( (nIndex
>= 0) && (nIndex
< xText
.getCharacter(i
)) )
300 aBuffer
.append( xText
.getCharacter(nIndex
) );
302 aBuffer
.append( '#' );
305 catch( IndexOutOfBoundsException e
)
308 return aBuffer
.toString();
312 private AccessibleTreeNode
getAttributes( XAccessibleText xText
,
313 AccessibleTreeNode aParent
)
315 String
[] aAttributeList
= new String
[] {
326 "ParaFirstLineIndent",
332 AccessibleTreeNode aRet
;
336 VectorNode aPortions
= new VectorNode ("getAttributes", aParent
);
339 int nLength
= xText
.getCharacterCount();
340 while( nIndex
< nLength
)
343 String aPortion
= null;
346 aPortion
= xText
.getTextAtIndex(
347 nIndex
, AccessibleTextType
.ATTRIBUTE_RUN
).SegmentText
;
349 catch(com
.sun
.star
.lang
.IllegalArgumentException e
)
354 // get attributes and make node with attribute children
355 PropertyValue
[] aValues
= xText
.getCharacterAttributes(nIndex
, aAttributeList
);
356 VectorNode aAttrs
= new VectorNode (aPortion
, aPortions
);
357 for( int i
= 0; i
< aValues
.length
; i
++ )
359 new StringNode( aValues
[i
].Name
+ ": " + aValues
[i
].Value
,
363 // get next portion, but advance at least one
364 nIndex
+= (aPortion
.length() > 0) ? aPortion
.length() : 1;
369 catch( UnknownPropertyException e
)
371 aRet
= new StringNode( "Exception caught:" + e
, aParent
);
373 catch( IndexOutOfBoundsException e
)
375 aRet
= new StringNode( "Exception caught:" + e
, aParent
);
382 private static String
[] aTextActions
=
383 new String
[] { "select...", "copy..." };
384 private static String
[] aEditableTextActions
=
385 new String
[] { "select...", "copy...",
386 "cut...", "paste...", "edit...", "format..." };
389 public String
[] getActions (AccessibleTreeNode aNode
)
391 XAccessibleEditableText xEText
= null;
392 if (aNode
instanceof AccTreeNode
)
393 xEText
= ((AccTreeNode
)aNode
).getEditText ();
395 return (xEText
== null) ? aTextActions
: aEditableTextActions
;
399 public void performAction (AccessibleTreeNode aNode
, int nIndex
)
401 if ( ! (aNode
instanceof AccTreeNode
))
404 AccTreeNode aATNode
= (AccTreeNode
)aNode
;
405 TextActionDialog aDialog
= null;
407 // create proper dialog
411 aDialog
= new TextActionDialog( aATNode
,
417 JTextComponent aText
, AccTreeNode aNode
)
418 throws IndexOutOfBoundsException
420 return aNode
.getText().setSelection(
427 aDialog
= new TextActionDialog( aATNode
,
428 "Select range and copy:",
433 JTextComponent aText
, AccTreeNode aNode
)
434 throws IndexOutOfBoundsException
436 return aNode
.getText().copyText(
443 aDialog
= new TextActionDialog( aATNode
,
444 "Select range and cut:",
449 JTextComponent aText
, AccTreeNode aNode
)
450 throws IndexOutOfBoundsException
452 return aNode
.getEditText().cutText(
459 aDialog
= new TextActionDialog( aATNode
,
460 "Place Caret and paste:",
465 JTextComponent aText
, AccTreeNode aNode
)
466 throws IndexOutOfBoundsException
468 return aNode
.getEditText().pasteText(
469 aText
.getCaretPosition() );
474 aDialog
= new TextEditDialog( aATNode
, "Edit text:",
478 aDialog
= new TextAttributeDialog( aATNode
);
482 if( aDialog
!= null )
483 aDialog
.setVisible(true);
489 * Display a dialog with a text field and a pair of cancel/do-it buttons
491 abstract class TextActionDialog
extends JDialog
492 implements ActionListener
494 private AccTreeNode aNode
;
496 private String sName
;
497 private JCheckBox aIndexToggle
;
499 public TextActionDialog( AccTreeNode aNd
,
503 super( AccessibilityWorkBench
.Instance() );
507 init( sExplanation
, aNode
.getText().getText(), sButtonText
);
512 protected void init( String sExplanation
,
518 // vertical stacking of the elements
519 Container aContent
= getContentPane();
521 // label with explanation
522 if( sExplanation
.length() > 0 )
523 aContent
.add( new JLabel( sExplanation
), BorderLayout
.NORTH
);
526 aText
= new JTextArea();
527 aText
.setText( sText
);
528 aText
.setColumns( Math
.min( Math
.max( 40, sText
.length() ), 20 ) );
529 aText
.setRows( sText
.length() / 40 + 1 );
530 aText
.setLineWrap( true );
531 aText
.setEditable( false );
532 aContent
.add( aText
, BorderLayout
.CENTER
);
534 JPanel aButtons
= new JPanel();
535 aButtons
.setLayout( new FlowLayout() );
536 aIndexToggle
= new JCheckBox( "reverse selection" );
537 aButtons
.add( aIndexToggle
);
538 JButton aActionButton
= new JButton( sButtonText
);
539 aActionButton
.setActionCommand( "Action" );
540 aActionButton
.addActionListener( this );
541 aButtons
.add( aActionButton
);
542 JButton aCancelButton
= new JButton( "cancel" );
543 aCancelButton
.setActionCommand( "Cancel" );
544 aCancelButton
.addActionListener( this );
545 aButtons
.add( aCancelButton
);
547 // add Panel with buttons
548 aContent
.add( aButtons
, BorderLayout
.SOUTH
);
551 private void cancel()
557 private void action()
559 String sError
= null;
562 boolean bSuccess
= action( aText
, aNode
);
564 sError
= "Can't execute";
566 catch( IndexOutOfBoundsException e
)
568 sError
= "Index out of bounds";
572 JOptionPane
.showMessageDialog( AccessibilityWorkBench
.Instance(),
574 JOptionPane
.ERROR_MESSAGE
);
579 public void actionPerformed(ActionEvent e
)
581 String sCommand
= e
.getActionCommand();
583 if( "Cancel".equals( sCommand
) )
585 else if( "Action".equals( sCommand
) )
590 int getSelectionStart() { return getSelection(true); }
591 int getSelectionEnd() { return getSelection(false); }
592 private int getSelection(boolean bStart
)
594 return ( bStart ^ aIndexToggle
.isSelected() )
595 ? aText
.getSelectionStart() : aText
.getSelectionEnd();
600 /** override this for dialog-specific action */
601 abstract boolean action( JTextComponent aText
, AccTreeNode aNode
)
602 throws IndexOutOfBoundsException
;
606 class TextEditDialog
extends TextActionDialog
608 public TextEditDialog( AccTreeNode aNode
,
612 super( aNode
, sExplanation
, sButtonText
);
616 protected void init( String sExplanation
,
620 super.init( sExplanation
, sText
, sButtonText
);
621 aText
.setEditable( true );
627 boolean action( JTextComponent aText
, AccTreeNode aNode
)
629 // is this text editable? if not, fudge you and return
630 XAccessibleEditableText xEdit
= aNode
.getEditText();
631 return ( xEdit
== null ) ?
false :
632 updateText( xEdit
, aText
.getText() );
636 /** update the text */
637 private boolean updateText( XAccessibleEditableText xEdit
, String sNew
)
639 String sOld
= xEdit
.getText();
641 // false alarm? Early out if no change was done!
642 if( sOld
.equals( sNew
) )
645 // get the minimum length of both strings
646 int nMinLength
= sOld
.length();
647 if( sNew
.length() < nMinLength
)
648 nMinLength
= sNew
.length();
650 // count equal characters from front and end
652 while( (nFront
< nMinLength
) &&
653 (sNew
.charAt(nFront
) == sOld
.charAt(nFront
)) ) {
657 while( (nBack
< nMinLength
) &&
658 ( sNew
.charAt(sNew
.length()-nBack
-1) ==
659 sOld
.charAt(sOld
.length()-nBack
-1) ) ) {
662 if( nFront
+ nBack
> nMinLength
)
663 nBack
= nMinLength
- nFront
;
665 // so... the first nFront and the last nBack characters
666 // are the same. Change the others!
667 String sDel
= sOld
.substring( nFront
, sOld
.length() - nBack
);
668 String sIns
= sNew
.substring( nFront
, sNew
.length() - nBack
);
670 System
.out
.println("edit text: " +
671 sOld
.substring(0, nFront
) +
672 " [ " + sDel
+ " -> " + sIns
+ " ] " +
673 sOld
.substring(sOld
.length() - nBack
) );
675 boolean bRet
= false;
678 // edit the text, and use
679 // (set|insert|delete|replace)Text as needed
680 if( nFront
+nBack
== 0 )
681 bRet
= xEdit
.setText( sIns
);
682 else if( sDel
.length() == 0 )
683 bRet
= xEdit
.insertText( sIns
, nFront
);
684 else if( sIns
.length() == 0 )
685 bRet
= xEdit
.deleteText( nFront
, sOld
.length()-nBack
);
687 bRet
= xEdit
.replaceText(nFront
, sOld
.length()-nBack
,sIns
);
689 catch( IndexOutOfBoundsException e
)
699 class TextAttributeDialog
extends TextActionDialog
701 public TextAttributeDialog(
704 super( aNode
, "Choose attributes, select text, and press 'Set':",
708 private JCheckBox aBold
, aUnderline
, aItalics
;
709 private Color aForeground
, aBackground
;
712 protected void init( String sExplanation
,
716 super.init( sExplanation
, sText
, sButtonText
);
718 aForeground
= Color
.black
;
719 aBackground
= Color
.white
;
721 JPanel aAttr
= new JPanel();
722 aAttr
.setLayout( new BoxLayout( aAttr
, BoxLayout
.Y_AXIS
) );
724 aBold
= new JCheckBox( "bold" );
725 aUnderline
= new JCheckBox( "underline" );
726 aItalics
= new JCheckBox( "italics" );
728 JButton aForeButton
= new JButton("Foreground", new ColorIcon(true));
729 aForeButton
.addActionListener( new ActionListener() {
730 public void actionPerformed(ActionEvent e
)
732 aForeground
= JColorChooser
.showDialog(
733 TextAttributeDialog
.this,
734 "Select Foreground Color",
739 JButton aBackButton
= new JButton("Background", new ColorIcon(false));
740 aBackButton
.addActionListener( new ActionListener() {
741 public void actionPerformed(ActionEvent e
)
743 aBackground
= JColorChooser
.showDialog(
744 TextAttributeDialog
.this,
745 "Select Background Color",
751 aAttr
.add( aUnderline
);
752 aAttr
.add( aItalics
);
753 aAttr
.add( aForeButton
);
754 aAttr
.add( aBackButton
);
756 getContentPane().add( aAttr
, BorderLayout
.WEST
);
760 private class ColorIcon
implements Icon
762 private boolean bForeground
;
763 private static final int nHeight
= 16;
764 private static final int nWidth
= 16;
766 public ColorIcon(boolean bWhich
) { bForeground
= bWhich
; }
767 public int getIconHeight() { return nHeight
; }
768 public int getIconWidth() { return nWidth
; }
769 public void paintIcon(Component c
, Graphics g
, int x
, int y
)
771 g
.setColor( getColor() );
772 g
.fillRect( x
, y
, nHeight
, nWidth
);
773 g
.setColor( c
.getForeground() );
774 g
.drawRect( x
, y
, nHeight
, nWidth
);
778 return bForeground ? aForeground
: aBackground
;
786 boolean action( JTextComponent aText
, AccTreeNode aNode
)
787 throws IndexOutOfBoundsException
789 // is this text editable? if not, fudge you and return
790 XAccessibleEditableText xEdit
= aNode
.getEditText();
791 boolean bSuccess
= false;
794 PropertyValue
[] aSequence
= new PropertyValue
[6];
795 aSequence
[0] = new PropertyValue();
796 aSequence
[0].Name
= "CharWeight";
797 aSequence
[0].Value
= Integer
.valueOf( aBold
.isSelected() ?
150 : 100 );
798 aSequence
[1] = new PropertyValue();
799 aSequence
[1].Name
= "CharUnderline";
800 aSequence
[1].Value
= Integer
.valueOf( aUnderline
.isSelected() ?
1 : 0 );
801 aSequence
[2] = new PropertyValue();
802 aSequence
[2].Name
= "CharBackColor";
803 aSequence
[2].Value
= Integer
.valueOf( aBackground
.getRGB() );
804 aSequence
[3] = new PropertyValue();
805 aSequence
[3].Name
= "CharColor";
806 aSequence
[3].Value
= Integer
.valueOf( aForeground
.getRGB() );
807 aSequence
[4] = new PropertyValue();
808 aSequence
[4].Name
= "CharPosture";
809 aSequence
[4].Value
= Integer
.valueOf( aItalics
.isSelected() ?
1 : 0 );
810 aSequence
[5] = new PropertyValue();
811 aSequence
[5].Name
= "CharBackTransparent";
812 aSequence
[5].Value
= Boolean
.FALSE
;
814 bSuccess
= xEdit
.setAttributes( getSelectionStart(),