blob: 14776cdda44314bf3f699288b63400fe4ca34b72 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
import com.sun.star.accessibility.AccessibleTextType;
import com.sun.star.accessibility.TextSegment;
import com.sun.star.accessibility.XAccessibleContext;
import com.sun.star.accessibility.XAccessibleText;
import com.sun.star.accessibility.XAccessibleEditableText;
import com.sun.star.awt.Rectangle;
import com.sun.star.awt.Point;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.beans.PropertyValue;
import java.util.Vector;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JDialog;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.Icon;
import javax.swing.JTextArea;
import javax.swing.JOptionPane;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.BoxLayout;
import javax.swing.text.JTextComponent;
class AccessibleTextHandler extends NodeHandler
{
public NodeHandler createHandler (XAccessibleContext xContext)
{
XAccessibleText xText = (XAccessibleText) UnoRuntime.queryInterface (
XAccessibleText.class, xContext);
if (xText != null)
return new AccessibleTextHandler (xText);
else
return null;
}
public AccessibleTextHandler ()
{
}
public AccessibleTextHandler (XAccessibleText xText)
{
if (xText != null)
maChildList.setSize (8);
}
public AccessibleTreeNode createChild (AccessibleTreeNode aParent, int nIndex)
{
AccessibleTreeNode aChild = null;
XAccessibleText xText = null;
if (aParent instanceof AccTreeNode)
xText = ((AccTreeNode)aParent).getText();
try
{
if( xText != null )
{
switch( nIndex )
{
case 0:
aChild = new StringNode (xText.getText(), aParent);
break;
case 1:
aChild = new StringNode ("# chars: " + xText.getCharacterCount(), aParent);
break;
case 2:
aChild = new StringNode (characters( xText ), aParent);
break;
case 3:
aChild = new StringNode ("selection: "
+ "[" + xText.getSelectionStart()
+ "," + xText.getSelectionEnd()
+ "] \"" + xText.getSelectedText() + "\"",
aParent);
break;
case 4:
aChild = new StringNode ("getCaretPosition: " + xText.getCaretPosition(), aParent);
break;
case 5:
{
VectorNode aVec = new VectorNode("portions", aParent);
aChild = aVec;
aVec.addChild(
textAtIndexNode( xText, "Character",
AccessibleTextType.CHARACTER,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Word",
AccessibleTextType.WORD,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Sentence",
AccessibleTextType.SENTENCE,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Paragraph",
AccessibleTextType.PARAGRAPH,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Line",
AccessibleTextType.LINE,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Attribute",
AccessibleTextType.ATTRIBUTE_RUN,
aParent ) );
aVec.addChild(
textAtIndexNode( xText, "Glyph",
AccessibleTextType.GLYPH,
aParent ) );
}
break;
case 6:
aChild = new StringNode (bounds( xText ), aParent);
break;
case 7:
aChild = getAttributes( xText, aParent );
break;
default:
aChild = new StringNode ("unknown child index " + nIndex, aParent);
}
}
}
catch (Exception e)
{
// Return empty child.
}
return aChild;
}
private String textAtIndexNodeString(
int nStart, int nEnd,
String sWord, String sBefore, String sBehind)
{
return "[" + nStart + "," + nEnd + "] "
+ "\"" + sWord + "\" \t"
+ "(" + sBefore + ","
+ "" + sBehind + ")";
}
/** Create a text node that lists all strings of a particular text type
*/
private AccessibleTreeNode textAtIndexNode(
XAccessibleText xText,
String sName,
short nTextType,
AccessibleTreeNode aParent)
{
VectorNode aNode = new VectorNode (sName, aParent);
// get word at all positions;
// for nicer display, compare current word to previous one and
// make a new node for every interval, not for every word
int nLength = xText.getCharacterCount();
if( nLength > 0 )
{
try
{
// sWord + nStart mark the current word
// make a node as soon as a new one is found; close the last
// one at the end
TextSegment sWord = xText.getTextAtIndex(0, nTextType);
TextSegment sBefore = xText.getTextBeforeIndex(0, nTextType);
TextSegment sBehind = xText.getTextBehindIndex(0, nTextType);
int nStart = 0;
for(int i = 1; i < nLength; i++)
{
TextSegment sTmp = xText.getTextAtIndex(i, nTextType);
TextSegment sTBef = xText.getTextBeforeIndex(i, nTextType);
TextSegment sTBeh = xText.getTextBehindIndex(i, nTextType);
if( ! ( sTmp.equals( sWord ) && sTBef.equals( sBefore ) &&
sTBeh.equals( sBehind ) ) )
{
aNode.addChild (new StringNode (textAtIndexNodeString(
nStart, i,
sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
sWord = sTmp;
sBefore = sTBef;
sBehind = sTBeh;
nStart = i;
}
// don't generate more than 50 children.
if (aNode.getChildCount() > 50)
{
sWord.SegmentText = "...";
break;
}
}
aNode.addChild (new StringNode (textAtIndexNodeString(
nStart, nLength,
sWord.SegmentText, sBefore.SegmentText, sBehind.SegmentText), aNode));
}
catch( IndexOutOfBoundsException e )
{
aNode.addChild (new StringNode (e.toString(), aNode));
}
catch (com.sun.star.lang.IllegalArgumentException e)
{
aNode.addChild (new StringNode (e.toString(), aNode));
}
}
return aNode;
}
/** getCharacter (display as array string) */
private String characters(XAccessibleText xText)
{
// get count (max. 30)
int nChars = xText.getCharacterCount();
if( nChars > 30 )
nChars = 30;
// build up string
StringBuffer aChars = new StringBuffer();
try
{
aChars.append( "[" );
for( int i = 0; i < nChars; i++)
{
aChars.append( xText.getCharacter(i) );
aChars.append( "," );
}
if( nChars > 0)
{
if( nChars == xText.getCharacterCount() )
aChars.deleteCharAt( aChars.length() - 1 );
else
aChars.append( "..." );
}
aChars.append( "]" );
}
catch( IndexOutOfBoundsException e )
{
aChars.append( " ERROR " );
}
// return result
return "getCharacters: " + aChars;
}
/** iterate over characters, and translate their positions
* back and forth */
private String bounds( XAccessibleText xText )
{
StringBuffer aBuffer = new StringBuffer( "bounds: " );
try
{
// iterate over characters
int nCount = xText.getCharacterCount();
for(int i = 0; i < nCount; i++ )
{
// get bounds for this character
Rectangle aRect = xText.getCharacterBounds( i );
// get the character by 'clicking' into the middle of
// the bounds
Point aMiddle = new Point();
aMiddle.X = aRect.X + (aRect.Width / 2) - 1;
aMiddle.Y = aRect.Y + (aRect.Height / 2 ) - 1;
int nIndex = xText.getIndexAtPoint( aMiddle );
// get the character, or a '#' for an illegal index
if( (nIndex >= 0) && (nIndex < xText.getCharacter(i)) )
aBuffer.append( xText.getCharacter(nIndex) );
else
aBuffer.append( '#' );
}
}
catch( IndexOutOfBoundsException e )
{ ; } // ignore errors
return aBuffer.toString();
}
private AccessibleTreeNode getAttributes( XAccessibleText xText,
AccessibleTreeNode aParent)
{
String[] aAttributeList = new String[] {
"CharBackColor",
"CharColor",
"CharEscapement",
"CharHeight",
"CharPosture",
"CharStrikeout",
"CharUnderline",
"CharWeight",
"ParaAdjust",
"ParaBottomMargin",
"ParaFirstLineIndent",
"ParaLeftMargin",
"ParaLineSpacing",
"ParaRightMargin",
"ParaTabStops"};
AccessibleTreeNode aRet;
try
{
VectorNode aPortions = new VectorNode ("getAttributes", aParent);
int nIndex = 0;
int nLength = xText.getCharacterCount();
while( nIndex < nLength )
{
// get attribute run
String aPortion = null;
try
{
aPortion = xText.getTextAtIndex(
nIndex, AccessibleTextType.ATTRIBUTE_RUN).SegmentText;
}
catch(com.sun.star.lang.IllegalArgumentException e)
{
aPortion = new String ("");
}
// get attributes and make node with attribute children
PropertyValue[] aValues = xText.getCharacterAttributes(nIndex, aAttributeList);
VectorNode aAttrs = new VectorNode (aPortion, aPortions);
for( int i = 0; i < aValues.length; i++ )
{
new StringNode( aValues[i].Name + ": " + aValues[i].Value,
aAttrs );
}
// get next portion, but advance at least one
nIndex += (aPortion.length() > 0) ? aPortion.length() : 1;
}
aRet = aPortions;
}
catch( IndexOutOfBoundsException e )
{
aRet = new StringNode( "Exception caught:" + e, aParent );
}
return aRet;
}
static String[] aTextActions =
new String[] { "select...", "copy..." };
static String[] aEditableTextActions =
new String[] { "select...", "copy...",
"cut...", "paste...", "edit...", "format..." };
public String[] getActions (AccessibleTreeNode aNode)
{
XAccessibleEditableText xEText = null;
if (aNode instanceof AccTreeNode)
xEText = ((AccTreeNode)aNode).getEditText ();
return (xEText == null) ? aTextActions : aEditableTextActions;
}
public void performAction (AccessibleTreeNode aNode, int nIndex)
{
if ( ! (aNode instanceof AccTreeNode))
return;
AccTreeNode aATNode = (AccTreeNode)aNode;
TextActionDialog aDialog = null;
// create proper dialog
switch( nIndex )
{
case 0:
aDialog = new TextActionDialog( aATNode,
"Select range:",
"select" )
{
boolean action(
JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
return aNode.getText().setSelection(
getSelectionStart(),
getSelectionEnd() );
}
};
break;
case 1:
aDialog = new TextActionDialog( aATNode,
"Select range and copy:",
"copy" )
{
boolean action(
JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
return aNode.getText().copyText(
getSelectionStart(),
getSelectionEnd() );
}
};
break;
case 2:
aDialog = new TextActionDialog( aATNode,
"Select range and cut:",
"cut" )
{
boolean action(
JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
return aNode.getEditText().cutText(
getSelectionStart(),
getSelectionEnd() );
}
};
break;
case 3:
aDialog = new TextActionDialog( aATNode,
"Place Caret and paste:",
"paste" )
{
boolean action(
JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
return aNode.getEditText().pasteText(
aText.getCaretPosition() );
}
};
break;
case 4:
aDialog = new TextEditDialog( aATNode, "Edit text:",
"edit" );
break;
case 5:
aDialog = new TextAttributeDialog( aATNode );
break;
}
if( aDialog != null )
aDialog.show();
}
}
/**
* Display a dialog with a text field and a pair of cancel/do-it buttons
*/
class TextActionDialog extends JDialog
implements ActionListener
{
AccTreeNode aNode;
JTextArea aText;
String sName;
JCheckBox aIndexToggle;
public TextActionDialog( AccTreeNode aNd,
String sExplanation,
String sButtonText )
{
super( AccessibilityWorkBench.Instance() );
aNode = aNd;
sName = sButtonText;
init( sExplanation, aNode.getText().getText(), sButtonText );
// setSize( getPreferredSize() );
setSize( 350, 225 );
}
/** build dialog */
protected void init( String sExplanation,
String sText,
String sButtonText )
{
setTitle( sName );
// vertical stacking of the elements
Container aContent = getContentPane();
// aContent.setLayout( new BorderLayout() );
// label with explanation
if( sExplanation.length() > 0 )
aContent.add( new JLabel( sExplanation ), BorderLayout.NORTH );
// the text field
aText = new JTextArea();
aText.setText( sText );
aText.setColumns( Math.min( Math.max( 40, sText.length() ), 20 ) );
aText.setRows( sText.length() / 40 + 1 );
aText.setLineWrap( true );
aText.setEditable( false );
aContent.add( aText, BorderLayout.CENTER );
JPanel aButtons = new JPanel();
aButtons.setLayout( new FlowLayout() );
aIndexToggle = new JCheckBox( "reverse selection" );
aButtons.add( aIndexToggle );
JButton aActionButton = new JButton( sButtonText );
aActionButton.setActionCommand( "Action" );
aActionButton.addActionListener( this );
aButtons.add( aActionButton );
JButton aCancelButton = new JButton( "cancel" );
aCancelButton.setActionCommand( "Cancel" );
aCancelButton.addActionListener( this );
aButtons.add( aCancelButton );
// add Panel with buttons
aContent.add( aButtons, BorderLayout.SOUTH );
}
void cancel()
{
hide();
dispose();
}
void action()
{
String sError = null;
try
{
boolean bSuccess = action( aText, aNode );
if( !bSuccess )
sError = "Can't execute";
}
catch( IndexOutOfBoundsException e )
{
sError = "Index out of bounds";
}
if( sError != null )
JOptionPane.showMessageDialog( AccessibilityWorkBench.Instance(),
sError, sName,
JOptionPane.ERROR_MESSAGE);
cancel();
}
public void actionPerformed(ActionEvent e)
{
String sCommand = e.getActionCommand();
if( "Cancel".equals( sCommand ) )
cancel();
else if( "Action".equals( sCommand ) )
action();
}
int getSelectionStart() { return getSelection(true); }
int getSelectionEnd() { return getSelection(false); }
int getSelection(boolean bStart)
{
return ( bStart ^ aIndexToggle.isSelected() )
? aText.getSelectionStart() : aText.getSelectionEnd();
}
/** override this for dialog-specific action */
boolean action( JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
return false;
}
}
class TextEditDialog extends TextActionDialog
{
public TextEditDialog( AccTreeNode aNode,
String sExplanation,
String sButtonText )
{
super( aNode, sExplanation, sButtonText );
}
protected void init( String sExplanation,
String sText,
String sButtonText )
{
super.init( sExplanation, sText, sButtonText );
aText.setEditable( true );
}
/** edit the text */
boolean action( JTextComponent aText, AccTreeNode aNode )
{
// is this text editable? if not, fudge you and return
XAccessibleEditableText xEdit = aNode.getEditText();
return ( xEdit == null ) ? false :
updateText( xEdit, aText.getText() );
}
/** update the text */
boolean updateText( XAccessibleEditableText xEdit, String sNew )
{
String sOld = xEdit.getText();
// false alarm? Early out if no change was done!
if( sOld.equals( sNew ) )
return false;
// get the minimum length of both strings
int nMinLength = sOld.length();
if( sNew.length() < nMinLength )
nMinLength = sNew.length();
// count equal characters from front and end
int nFront = 0;
while( (nFront < nMinLength) &&
(sNew.charAt(nFront) == sOld.charAt(nFront)) )
nFront++;
int nBack = 0;
while( (nBack < nMinLength) &&
( sNew.charAt(sNew.length()-nBack-1) ==
sOld.charAt(sOld.length()-nBack-1) ) )
nBack++;
if( nFront + nBack > nMinLength )
nBack = nMinLength - nFront;
// so... the first nFront and the last nBack characters
// are the same. Change the others!
String sDel = sOld.substring( nFront, sOld.length() - nBack );
String sIns = sNew.substring( nFront, sNew.length() - nBack );
System.out.println("edit text: " +
sOld.substring(0, nFront) +
" [ " + sDel + " -> " + sIns + " ] " +
sOld.substring(sOld.length() - nBack) );
boolean bRet = false;
try
{
// edit the text, and use
// (set|insert|delete|replace)Text as needed
if( nFront+nBack == 0 )
bRet = xEdit.setText( sIns );
else if( sDel.length() == 0 )
bRet = xEdit.insertText( sIns, nFront );
else if( sIns.length() == 0 )
bRet = xEdit.deleteText( nFront, sOld.length()-nBack );
else
bRet = xEdit.replaceText(nFront, sOld.length()-nBack,sIns);
}
catch( IndexOutOfBoundsException e )
{
bRet = false;
}
return bRet;
}
}
class TextAttributeDialog extends TextActionDialog
{
public TextAttributeDialog(
AccTreeNode aNode )
{
super( aNode, "Choose attributes, select text, and press 'Set':",
"set" );
}
private JCheckBox aBold, aUnderline, aItalics;
private Color aForeground, aBackground;
protected void init( String sExplanation,
String sText,
String sButtonText )
{
super.init( sExplanation, sText, sButtonText );
aForeground = Color.black;
aBackground = Color.white;
JPanel aAttr = new JPanel();
aAttr.setLayout( new BoxLayout( aAttr, BoxLayout.Y_AXIS ) );
aBold = new JCheckBox( "bold" );
aUnderline = new JCheckBox( "underline" );
aItalics = new JCheckBox( "italics" );
JButton aForeButton = new JButton("Foreground", new ColorIcon(true));
aForeButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e)
{
aForeground = JColorChooser.showDialog(
TextAttributeDialog.this,
"Select Foreground Color",
aForeground);
}
} );
JButton aBackButton = new JButton("Background", new ColorIcon(false));
aBackButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e)
{
aBackground = JColorChooser.showDialog(
TextAttributeDialog.this,
"Select Background Color",
aBackground);
}
} );
aAttr.add( aBold );
aAttr.add( aUnderline );
aAttr.add( aItalics );
aAttr.add( aForeButton );
aAttr.add( aBackButton );
getContentPane().add( aAttr, BorderLayout.WEST );
}
class ColorIcon implements Icon
{
boolean bForeground;
static final int nHeight = 16;
static final int nWidth = 16;
public ColorIcon(boolean bWhich) { bForeground = bWhich; }
public int getIconHeight() { return nHeight; }
public int getIconWidth() { return nWidth; }
public void paintIcon(Component c, Graphics g, int x, int y)
{
g.setColor( getColor() );
g.fillRect( x, y, nHeight, nWidth );
g.setColor( c.getForeground() );
g.drawRect( x, y, nHeight, nWidth );
}
Color getColor()
{
return bForeground ? aForeground : aBackground;
}
}
/** edit the text */
boolean action( JTextComponent aText, AccTreeNode aNode )
throws IndexOutOfBoundsException
{
// is this text editable? if not, fudge you and return
XAccessibleEditableText xEdit = aNode.getEditText();
boolean bSuccess = false;
if( xEdit != null )
{
PropertyValue[] aSequence = new PropertyValue[6];
aSequence[0] = new PropertyValue();
aSequence[0].Name = "CharWeight";
aSequence[0].Value = new Integer( aBold.isSelected() ? 150 : 100 );
aSequence[1] = new PropertyValue();
aSequence[1].Name = "CharUnderline";
aSequence[1].Value = new Integer( aUnderline.isSelected() ? 1 : 0 );
aSequence[2] = new PropertyValue();
aSequence[2].Name = "CharBackColor";
aSequence[2].Value = new Integer( aBackground.getRGB() );
aSequence[3] = new PropertyValue();
aSequence[3].Name = "CharColor";
aSequence[3].Value = new Integer( aForeground.getRGB() );
aSequence[4] = new PropertyValue();
aSequence[4].Name = "CharPosture";
aSequence[4].Value = new Integer( aItalics.isSelected() ? 1 : 0 );
aSequence[5] = new PropertyValue();
aSequence[5].Name = "CharBackTransparent";
aSequence[5].Value = new Boolean( false );
bSuccess = xEdit.setAttributes( getSelectionStart(),
getSelectionEnd(),
aSequence );
}
return bSuccess;
}
}