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
);
43 return new AccessibleTextHandler (xText
);
48 public AccessibleTextHandler ()
52 public AccessibleTextHandler (XAccessibleText xText
)
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();
72 aChild
= new StringNode (xText
.getText(), aParent
);
75 aChild
= new StringNode ("# chars: " + xText
.getCharacterCount(), aParent
);
78 aChild
= new StringNode (characters( xText
), aParent
);
81 aChild
= new StringNode ("selection: "
82 + "[" + xText
.getSelectionStart()
83 + "," + xText
.getSelectionEnd()
84 + "] \"" + xText
.getSelectedText() + "\"",
88 aChild
= new StringNode ("getCaretPosition: " + xText
.getCaretPosition(), aParent
);
92 VectorNode aVec
= new VectorNode("portions", aParent
);
95 textAtIndexNode( xText
, "Character",
96 AccessibleTextType
.CHARACTER
,
99 textAtIndexNode( xText
, "Word",
100 AccessibleTextType
.WORD
,
103 textAtIndexNode( xText
, "Sentence",
104 AccessibleTextType
.SENTENCE
,
107 textAtIndexNode( xText
, "Paragraph",
108 AccessibleTextType
.PARAGRAPH
,
111 textAtIndexNode( xText
, "Line",
112 AccessibleTextType
.LINE
,
115 textAtIndexNode( xText
, "Attribute",
116 AccessibleTextType
.ATTRIBUTE_RUN
,
119 textAtIndexNode( xText
, "Glyph",
120 AccessibleTextType
.GLYPH
,
125 aChild
= new StringNode (bounds( xText
), aParent
);
128 aChild
= getAttributes( xText
, aParent
);
131 aChild
= new StringNode ("unknown child index " + nIndex
, aParent
);
137 // Return empty child.
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
,
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();
172 // sWord + nStart mark the current word
173 // make a node as soon as a new one is found; close the last
175 TextSegment sWord
= xText
.getTextAtIndex(0, nTextType
);
176 TextSegment sBefore
= xText
.getTextBeforeIndex(0, nTextType
);
177 TextSegment sBehind
= xText
.getTextBehindIndex(0, nTextType
);
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(
189 sWord
.SegmentText
, sBefore
.SegmentText
, sBehind
.SegmentText
), aNode
));
196 // don't generate more than 50 children.
197 if (aNode
.getChildCount() > 50)
199 sWord
.SegmentText
= "...";
203 aNode
.addChild (new StringNode (textAtIndexNodeString(
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
));
222 /** getCharacter (display as array string) */
223 private String
characters(XAccessibleText xText
)
225 // get count (max. 30)
226 int nChars
= xText
.getCharacterCount();
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( "," );
242 if( nChars
== xText
.getCharacterCount() )
243 aChars
.deleteCharAt( aChars
.length() - 1 );
245 aChars
.append( "..." );
247 aChars
.append( "]" );
249 catch( IndexOutOfBoundsException e
)
251 aChars
.append( " ERROR " );
255 return "getCharacters: " + aChars
;
259 /** iterate over characters, and translate their positions
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
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
) );
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
[] {
308 "ParaFirstLineIndent",
314 AccessibleTreeNode aRet
;
318 VectorNode aPortions
= new VectorNode ("getAttributes", aParent
);
321 int nLength
= xText
.getCharacterCount();
322 while( nIndex
< nLength
)
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
,
345 // get next portion, but advance at least one
346 nIndex
+= (aPortion
.length() > 0) ? aPortion
.length() : 1;
351 catch( IndexOutOfBoundsException e
)
353 aRet
= new StringNode( "Exception caught:" + e
, aParent
);
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
))
380 AccTreeNode aATNode
= (AccTreeNode
)aNode
;
381 TextActionDialog aDialog
= null;
383 // create proper dialog
387 aDialog
= new TextActionDialog( aATNode
,
392 JTextComponent aText
, AccTreeNode aNode
)
393 throws IndexOutOfBoundsException
395 return aNode
.getText().setSelection(
402 aDialog
= new TextActionDialog( aATNode
,
403 "Select range and copy:",
407 JTextComponent aText
, AccTreeNode aNode
)
408 throws IndexOutOfBoundsException
410 return aNode
.getText().copyText(
417 aDialog
= new TextActionDialog( aATNode
,
418 "Select range and cut:",
422 JTextComponent aText
, AccTreeNode aNode
)
423 throws IndexOutOfBoundsException
425 return aNode
.getEditText().cutText(
432 aDialog
= new TextActionDialog( aATNode
,
433 "Place Caret and paste:",
437 JTextComponent aText
, AccTreeNode aNode
)
438 throws IndexOutOfBoundsException
440 return aNode
.getEditText().pasteText(
441 aText
.getCaretPosition() );
446 aDialog
= new TextEditDialog( aATNode
, "Edit text:",
450 aDialog
= new TextAttributeDialog( aATNode
);
454 if( aDialog
!= null )
461 * Display a dialog with a text field and a pair of cancel/do-it buttons
463 class TextActionDialog
extends JDialog
464 implements ActionListener
469 JCheckBox aIndexToggle
;
471 public TextActionDialog( AccTreeNode aNd
,
475 super( AccessibilityWorkBench
.Instance() );
479 init( sExplanation
, aNode
.getText().getText(), sButtonText
);
480 // setSize( getPreferredSize() );
485 protected void init( String sExplanation
,
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
);
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
);
533 String sError
= null;
536 boolean bSuccess
= action( aText
, aNode
);
538 sError
= "Can't execute";
540 catch( IndexOutOfBoundsException e
)
542 sError
= "Index out of bounds";
546 JOptionPane
.showMessageDialog( AccessibilityWorkBench
.Instance(),
548 JOptionPane
.ERROR_MESSAGE
);
553 public void actionPerformed(ActionEvent e
)
555 String sCommand
= e
.getActionCommand();
557 if( "Cancel".equals( sCommand
) )
559 else if( "Action".equals( sCommand
) )
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
583 class TextEditDialog
extends TextActionDialog
585 public TextEditDialog( AccTreeNode aNode
,
589 super( aNode
, sExplanation
, sButtonText
);
592 protected void init( String sExplanation
,
596 super.init( sExplanation
, sText
, sButtonText
);
597 aText
.setEditable( true );
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
) )
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
627 while( (nFront
< nMinLength
) &&
628 (sNew
.charAt(nFront
) == sOld
.charAt(nFront
)) )
631 while( (nBack
< nMinLength
) &&
632 ( sNew
.charAt(sNew
.length()-nBack
-1) ==
633 sOld
.charAt(sOld
.length()-nBack
-1) ) )
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
);
660 bRet
= xEdit
.replaceText(nFront
, sOld
.length()-nBack
,sIns
);
662 catch( IndexOutOfBoundsException e
)
672 class TextAttributeDialog
extends TextActionDialog
674 public TextAttributeDialog(
677 super( aNode
, "Choose attributes, select text, and press 'Set':",
681 private JCheckBox aBold
, aUnderline
, aItalics
;
682 private Color aForeground
, aBackground
;
684 protected void init( String sExplanation
,
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",
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",
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
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
);
750 return bForeground ? aForeground
: aBackground
;
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;
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(),