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
);
59 return new AccessibleTextHandler (xText
);
64 public AccessibleTextHandler ()
68 public AccessibleTextHandler (XAccessibleText xText
)
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();
88 aChild
= new StringNode (xText
.getText(), aParent
);
91 aChild
= new StringNode ("# chars: " + xText
.getCharacterCount(), aParent
);
94 aChild
= new StringNode (characters( xText
), aParent
);
97 aChild
= new StringNode ("selection: "
98 + "[" + xText
.getSelectionStart()
99 + "," + xText
.getSelectionEnd()
100 + "] \"" + xText
.getSelectedText() + "\"",
104 aChild
= new StringNode ("getCaretPosition: " + xText
.getCaretPosition(), aParent
);
108 VectorNode aVec
= new VectorNode("portions", aParent
);
111 textAtIndexNode( xText
, "Character",
112 AccessibleTextType
.CHARACTER
,
115 textAtIndexNode( xText
, "Word",
116 AccessibleTextType
.WORD
,
119 textAtIndexNode( xText
, "Sentence",
120 AccessibleTextType
.SENTENCE
,
123 textAtIndexNode( xText
, "Paragraph",
124 AccessibleTextType
.PARAGRAPH
,
127 textAtIndexNode( xText
, "Line",
128 AccessibleTextType
.LINE
,
131 textAtIndexNode( xText
, "Attribute",
132 AccessibleTextType
.ATTRIBUTE_RUN
,
135 textAtIndexNode( xText
, "Glyph",
136 AccessibleTextType
.GLYPH
,
141 aChild
= new StringNode (bounds( xText
), aParent
);
144 aChild
= getAttributes( xText
, aParent
);
147 aChild
= new StringNode ("unknown child index " + nIndex
, aParent
);
153 // Return empty child.
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
,
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();
188 // sWord + nStart mark the current word
189 // make a node as soon as a new one is found; close the last
191 TextSegment sWord
= xText
.getTextAtIndex(0, nTextType
);
192 TextSegment sBefore
= xText
.getTextBeforeIndex(0, nTextType
);
193 TextSegment sBehind
= xText
.getTextBehindIndex(0, nTextType
);
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(
205 sWord
.SegmentText
, sBefore
.SegmentText
, sBehind
.SegmentText
), aNode
));
212 // don't generate more than 50 children.
213 if (aNode
.getChildCount() > 50)
215 sWord
.SegmentText
= "...";
219 aNode
.addChild (new StringNode (textAtIndexNodeString(
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
));
238 /** getCharacter (display as array string) */
239 private String
characters(XAccessibleText xText
)
241 // get count (max. 30)
242 int nChars
= xText
.getCharacterCount();
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( "," );
258 if( nChars
== xText
.getCharacterCount() )
259 aChars
.deleteCharAt( aChars
.length() - 1 );
261 aChars
.append( "..." );
263 aChars
.append( "]" );
265 catch( IndexOutOfBoundsException e
)
267 aChars
.append( " ERROR " );
271 return "getCharacters: " + aChars
;
275 /** iterate over characters, and translate their positions
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
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
) );
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
[] {
324 "ParaFirstLineIndent",
330 AccessibleTreeNode aRet
;
334 VectorNode aPortions
= new VectorNode ("getAttributes", aParent
);
337 int nLength
= xText
.getCharacterCount();
338 while( nIndex
< nLength
)
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
,
361 // get next portion, but advance at least one
362 nIndex
+= (aPortion
.length() > 0) ? aPortion
.length() : 1;
367 catch( IndexOutOfBoundsException e
)
369 aRet
= new StringNode( "Exception caught:" + e
, aParent
);
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
))
396 AccTreeNode aATNode
= (AccTreeNode
)aNode
;
397 TextActionDialog aDialog
= null;
399 // create proper dialog
403 aDialog
= new TextActionDialog( aATNode
,
408 JTextComponent aText
, AccTreeNode aNode
)
409 throws IndexOutOfBoundsException
411 return aNode
.getText().setSelection(
418 aDialog
= new TextActionDialog( aATNode
,
419 "Select range and copy:",
423 JTextComponent aText
, AccTreeNode aNode
)
424 throws IndexOutOfBoundsException
426 return aNode
.getText().copyText(
433 aDialog
= new TextActionDialog( aATNode
,
434 "Select range and cut:",
438 JTextComponent aText
, AccTreeNode aNode
)
439 throws IndexOutOfBoundsException
441 return aNode
.getEditText().cutText(
448 aDialog
= new TextActionDialog( aATNode
,
449 "Place Caret and paste:",
453 JTextComponent aText
, AccTreeNode aNode
)
454 throws IndexOutOfBoundsException
456 return aNode
.getEditText().pasteText(
457 aText
.getCaretPosition() );
462 aDialog
= new TextEditDialog( aATNode
, "Edit text:",
466 aDialog
= new TextAttributeDialog( aATNode
);
470 if( aDialog
!= null )
477 * Display a dialog with a text field and a pair of cancel/do-it buttons
479 class TextActionDialog
extends JDialog
480 implements ActionListener
485 JCheckBox aIndexToggle
;
487 public TextActionDialog( AccTreeNode aNd
,
491 super( AccessibilityWorkBench
.Instance() );
495 init( sExplanation
, aNode
.getText().getText(), sButtonText
);
496 // setSize( getPreferredSize() );
501 protected void init( String sExplanation
,
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
);
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
);
549 String sError
= null;
552 boolean bSuccess
= action( aText
, aNode
);
554 sError
= "Can't execute";
556 catch( IndexOutOfBoundsException e
)
558 sError
= "Index out of bounds";
562 JOptionPane
.showMessageDialog( AccessibilityWorkBench
.Instance(),
564 JOptionPane
.ERROR_MESSAGE
);
569 public void actionPerformed(ActionEvent e
)
571 String sCommand
= e
.getActionCommand();
573 if( "Cancel".equals( sCommand
) )
575 else if( "Action".equals( sCommand
) )
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
599 class TextEditDialog
extends TextActionDialog
601 public TextEditDialog( AccTreeNode aNode
,
605 super( aNode
, sExplanation
, sButtonText
);
608 protected void init( String sExplanation
,
612 super.init( sExplanation
, sText
, sButtonText
);
613 aText
.setEditable( true );
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
) )
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
643 while( (nFront
< nMinLength
) &&
644 (sNew
.charAt(nFront
) == sOld
.charAt(nFront
)) )
647 while( (nBack
< nMinLength
) &&
648 ( sNew
.charAt(sNew
.length()-nBack
-1) ==
649 sOld
.charAt(sOld
.length()-nBack
-1) ) )
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
);
676 bRet
= xEdit
.replaceText(nFront
, sOld
.length()-nBack
,sIns
);
678 catch( IndexOutOfBoundsException e
)
688 class TextAttributeDialog
extends TextActionDialog
690 public TextAttributeDialog(
693 super( aNode
, "Choose attributes, select text, and press 'Set':",
697 private JCheckBox aBold
, aUnderline
, aItalics
;
698 private Color aForeground
, aBackground
;
700 protected void init( String sExplanation
,
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",
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",
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
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
);
766 return bForeground ? aForeground
: aBackground
;
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;
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(),