1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.accessibility.awb.view;
25 
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.GridBagConstraints;
29 import java.awt.GridBagLayout;
30 import java.awt.event.ActionListener;
31 import java.awt.event.ActionEvent;
32 
33 import javax.swing.JButton;
34 import javax.swing.JComponent;
35 import javax.swing.JLabel;
36 import javax.swing.JPanel;
37 import javax.swing.JScrollPane;
38 import javax.swing.JSpinner;
39 import javax.swing.JTree;
40 import javax.swing.tree.TreeNode;
41 import javax.swing.tree.DefaultMutableTreeNode;
42 import javax.swing.tree.DefaultTreeModel;
43 import javax.swing.tree.MutableTreeNode;
44 
45 import com.sun.star.accessibility.AccessibleEventId;
46 import com.sun.star.accessibility.AccessibleEventObject;
47 import com.sun.star.accessibility.AccessibleTextType;
48 import com.sun.star.accessibility.AccessibleStateType;
49 import com.sun.star.accessibility.TextSegment;
50 import com.sun.star.accessibility.XAccessibleText;
51 import com.sun.star.accessibility.XAccessibleContext;
52 import com.sun.star.accessibility.XAccessibleMultiLineText;
53 import com.sun.star.accessibility.XAccessibleStateSet;
54 import com.sun.star.awt.Point;
55 import com.sun.star.awt.Rectangle;
56 import com.sun.star.beans.PropertyValue;
57 import com.sun.star.lang.IndexOutOfBoundsException;
58 import com.sun.star.lang.IllegalArgumentException;
59 import com.sun.star.uno.UnoRuntime;
60 
61 import org.openoffice.accessibility.awb.view.text.CaretSpinnerModel;
62 import org.openoffice.accessibility.awb.view.text.TextDialogFactory;
63 
64 
65 public class TextView
66     extends ObjectView
67     implements ActionListener
68 {
69 
70     /** Create a TextView when the given object supports the
71         XAccessibleText interface.
72     */
Create( ObjectViewContainer aContainer, XAccessibleContext xContext)73     static public ObjectView Create (
74         ObjectViewContainer aContainer,
75         XAccessibleContext xContext)
76     {
77         XAccessibleText xText = (XAccessibleText)UnoRuntime.queryInterface(
78                 XAccessibleText.class, xContext);
79         if (xText != null)
80             return new TextView (aContainer);
81         else
82             return null;
83     }
84 
85 
TextView(ObjectViewContainer aContainer)86     public TextView (ObjectViewContainer aContainer)
87     {
88         super (aContainer);
89 
90         ViewGridLayout aLayout = new ViewGridLayout (this);
91 
92         maTextLabel = aLayout.AddLabeledString ("Text: ");
93         maCharacterArrayLabel = aLayout.AddLabeledEntry ("Characters: ");
94         maCharacterCountLabel = aLayout.AddLabeledEntry ("Character Count: ");
95         maSelectionLabel = aLayout.AddLabeledEntry ("Selection: ");
96         maBoundsLabel = aLayout.AddLabeledEntry ("Bounds Test: ");
97         maCaretPositionSpinner = (JSpinner)aLayout.AddLabeledComponent (
98             "Caret position:", new JSpinner());
99         Dimension aSize = maCaretPositionSpinner.getSize();
100         maCaretPositionSpinner.setPreferredSize (new Dimension (100,20));
101         maCaretLineNoLabel = aLayout.AddLabeledEntry ("Line number at caret: ");
102         maCaretLineTextLabel = aLayout.AddLabeledEntry ("Text of line at caret: ");
103         maLineNoFromCaretPosLabel = aLayout.AddLabeledEntry ("Line number at index of caret: ");
104         maLineTextFromCaretPosLabel = aLayout.AddLabeledEntry ("Text of line at index of caret: ");
105 
106         JPanel aButtonPanel = new JPanel ();
107         aLayout.AddComponent (aButtonPanel);
108 
109         JButton aButton = new JButton ("select...");
110         aButton.setFont (aLayout.GetFont());
111         aButton.addActionListener (this);
112         aButtonPanel.add (aButton);
113 
114         aButton = new JButton ("copy...");
115         aButton.setFont (aLayout.GetFont());
116         aButton.addActionListener (this);
117         aButtonPanel.add (aButton);
118 
119         // A tree that holds the text broken down into various segments.
120         maTree = new JTree ();
121         aLayout.AddComponent (new JScrollPane (
122             maTree,
123             JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
124             JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
125     }
126 
127 
128     /** Additionally to the context store a reference to the
129         XAccessibleText interface.
130     */
SetObject(XAccessibleContext xObject)131     public void SetObject (XAccessibleContext xObject)
132     {
133         mxText = (XAccessibleText)UnoRuntime.queryInterface(
134             XAccessibleText.class, xObject);
135         maCaretSpinnerModel = new CaretSpinnerModel(mxText);
136         maCaretPositionSpinner.setModel (maCaretSpinnerModel);
137         super.SetObject (xObject);
138     }
139 
Destroy()140     synchronized public void Destroy ()
141     {
142         mxText = null;
143         super.Destroy();
144     }
145 
Update()146     synchronized public void Update ()
147     {
148         maCaretPositionSpinner.setEnabled (mxText != null);
149         DefaultMutableTreeNode aRoot = new DefaultMutableTreeNode ("Text Segments");
150         if (mxText == null)
151         {
152             maTextLabel.setText ("<null object>");
153             maCharacterArrayLabel.setText ("<null object>");
154             maCharacterCountLabel.setText ("<null object>");
155             maSelectionLabel.setText ("<null object>");
156             maBoundsLabel.setText ("<null object>");
157             maCaretLineNoLabel.setText ("<null object>");
158             maCaretLineTextLabel.setText ("<null object>");
159             maLineNoFromCaretPosLabel.setText ("<null object>");
160             maLineTextFromCaretPosLabel.setText ("<null object>");
161         }
162         else
163         {
164             maTextLabel.setText (mxText.getText());
165             maCharacterArrayLabel.setText (GetCharacterArray());
166             maCharacterCountLabel.setText (
167                 Integer.toString(mxText.getCharacterCount()));
168             // Selection.
169             maSelectionLabel.setText (
170                 "[" + mxText.getSelectionStart()
171                 + "," + mxText.getSelectionEnd()
172                 + "] \"" + mxText.getSelectedText() + "\"");
173 
174             // Character bounds.
175             maBoundsLabel.setText (GetTextBoundsString());
176 
177             // Caret position.
178             maCaretPositionSpinner.setValue (new Integer (mxText.getCaretPosition()));
179 
180             // Multi line methods.
181             XAccessibleMultiLineText xMultiText = (XAccessibleMultiLineText)
182                 UnoRuntime.queryInterface( XAccessibleMultiLineText.class, mxText );
183 
184             if( null != xMultiText ) {
185                 try {
186                   maCaretLineNoLabel.setText ( Integer.toString( xMultiText.getNumberOfLineWithCaret() ) );
187                   TextSegment ts = xMultiText.getTextAtLineWithCaret();
188                   maCaretLineTextLabel.setText ( "[" + ts.SegmentStart
189                      + "," + ts.SegmentEnd
190                      + "] \"" + ts.SegmentText + "\"");
191                   maLineNoFromCaretPosLabel.setText ( Integer.toString( xMultiText.getLineNumberAtIndex( mxText.getCaretPosition() ) ) );
192                   ts = xMultiText.getTextAtLineNumber(xMultiText.getLineNumberAtIndex( mxText.getCaretPosition() ) );
193                   maLineTextFromCaretPosLabel.setText  ( "[" + ts.SegmentStart
194                     + "," + ts.SegmentEnd
195                     + "] \"" + ts.SegmentText + "\"");
196                 } catch( IndexOutOfBoundsException e) {
197                 }
198             }
199 
200             // Text segments.
201             aRoot.add (CreateNode ("Character", AccessibleTextType.CHARACTER));
202             aRoot.add (CreateNode ("Word", AccessibleTextType.WORD));
203             aRoot.add (CreateNode ("Sentence", AccessibleTextType.SENTENCE));
204             aRoot.add (CreateNode ("Paragraph", AccessibleTextType.PARAGRAPH));
205             aRoot.add (CreateNode ("Line", AccessibleTextType.LINE));
206             aRoot.add (CreateNode ("Attribute", AccessibleTextType.ATTRIBUTE_RUN));
207             aRoot.add (CreateNode ("Glyph", AccessibleTextType.GLYPH));
208         }
209         ((DefaultTreeModel)maTree.getModel()).setRoot (aRoot);
210     }
211 
GetTitle()212     public String GetTitle ()
213     {
214         return ("Text");
215     }
216 
notifyEvent(AccessibleEventObject aEvent)217     public void notifyEvent (AccessibleEventObject aEvent)
218     {
219         System.out.println (aEvent);
220         switch (aEvent.EventId)
221         {
222             case AccessibleEventId.CARET_CHANGED :
223                 maCaretSpinnerModel.Update();
224                 Update ();
225                 break;
226 
227             case AccessibleEventId.TEXT_CHANGED :
228             case AccessibleEventId.TEXT_SELECTION_CHANGED:
229                 Update ();
230                 break;
231         }
232     }
233 
actionPerformed(ActionEvent aEvent)234     public void actionPerformed (ActionEvent aEvent)
235     {
236         String sCommand = aEvent.getActionCommand();
237         if (sCommand.equals ("select..."))
238             TextDialogFactory.CreateSelectionDialog (mxContext);
239         else if (sCommand.equals ("copy..."))
240             TextDialogFactory.CreateCopyDialog (mxContext);
241     }
242 
243 
244 
245     /** Create a string that is a list of all characters returned by the
246         getCharacter() method.
247     */
GetCharacterArray()248     private String GetCharacterArray ()
249     {
250         // Do not show more than 30 characters.
251         int nCharacterCount = mxText.getCharacterCount();
252         int nMaxDisplayCount = 30;
253 
254         // build up string
255         StringBuffer aCharacterArray = new StringBuffer();
256         int nIndex = 0;
257         try
258         {
259             while (nIndex<nCharacterCount && nIndex<nMaxDisplayCount)
260             {
261                 aCharacterArray.append (mxText.getCharacter (nIndex));
262                 if (nIndex < nCharacterCount-1)
263                     aCharacterArray.append (",");
264                 nIndex ++;
265             }
266             if (nMaxDisplayCount < nCharacterCount)
267                 aCharacterArray.append (", ...");
268         }
269         catch (IndexOutOfBoundsException e)
270         {
271             aCharacterArray.append ("; Index Out Of Bounds at index " + nIndex);
272         }
273 
274         return aCharacterArray.toString();
275     }
276 
277 
278 
279     /** Iterate over all characters and translate their positions
280         back and forth.
281         */
GetTextBoundsString()282     private String GetTextBoundsString ()
283     {
284         StringBuffer aBuffer = new StringBuffer ();
285         try
286         {
287             // Iterate over all characters in the text.
288             int nCount = mxText.getCharacterCount();
289             for (int i=0; i<nCount; i++)
290             {
291                 // Get bounds for this character.
292                 Rectangle aBBox = mxText.getCharacterBounds (i);
293 
294                 // get the character by 'clicking' into the middle of
295                 // the bounds
296                 Point aMiddle = new Point();
297                 aMiddle.X = aBBox.X + (aBBox.Width / 2) - 1;
298                 aMiddle.Y = aBBox.Y + (aBBox.Height / 2) - 1;
299                 int nIndex = mxText.getIndexAtPoint (aMiddle);
300 
301                 // get the character, or a '#' for an illegal index
302                 if ((nIndex >= 0) && (nIndex < mxText.getCharacter(i)))
303                     aBuffer.append (mxText.getCharacter(nIndex));
304                 else
305                     aBuffer.append ('#');
306             }
307         }
308         catch (IndexOutOfBoundsException aEvent)
309         {
310             // Ignore errors.
311         }
312 
313         return aBuffer.toString();
314     }
315 
316 
317 
318 
319     private final static int BEFORE = -1;
320     private final static int AT = 0;
321     private final static int BEHIND = +1;
322 
CreateNode(String sTitle, short nTextType)323     private MutableTreeNode CreateNode (String sTitle, short nTextType)
324     {
325         DefaultMutableTreeNode aNode = new DefaultMutableTreeNode (sTitle);
326 
327         aNode.add (CreateSegmentNode ("Before", nTextType, BEFORE));
328         aNode.add (CreateSegmentNode ("At", nTextType, AT));
329         aNode.add (CreateSegmentNode ("Behind", nTextType, BEHIND));
330 
331         return aNode;
332     }
333 
CreateSegmentNode(String sTitle, short nTextType, int nWhere)334     private MutableTreeNode CreateSegmentNode (String sTitle, short nTextType, int nWhere)
335     {
336         TextSegment aSegment;
337         int nTextLength = mxText.getCharacterCount();
338         DefaultMutableTreeNode aNode = new DefaultMutableTreeNode (sTitle);
339         for (int nIndex=0; nIndex<=nTextLength; /* empty */)
340         {
341             aSegment = GetTextSegment (nIndex, nTextType, nWhere);
342             DefaultMutableTreeNode aSegmentNode = new DefaultMutableTreeNode (
343                 new StringBuffer (
344                     Integer.toString (nIndex) + " -> "
345                     + Integer.toString (aSegment.SegmentStart) + " - "
346                     + Integer.toString (aSegment.SegmentEnd) + " : "
347                     + aSegment.SegmentText.toString()));
348             aNode.add (aSegmentNode);
349             if (nTextType ==  AccessibleTextType.ATTRIBUTE_RUN)
350                 AddAttributeNodes (aSegmentNode, aSegment);
351             if (aSegment.SegmentEnd > nIndex)
352                 nIndex = aSegment.SegmentEnd;
353             else
354                 nIndex ++;
355         }
356 
357         return aNode;
358     }
359 
360 
GetTextSegment(int nIndex, short nTextType, int nWhere)361     private TextSegment GetTextSegment (int nIndex, short nTextType, int nWhere)
362     {
363         TextSegment aSegment;
364 
365         try
366         {
367             switch (nWhere)
368             {
369                 case BEFORE:
370                     aSegment = mxText.getTextBeforeIndex (nIndex, nTextType);
371                     break;
372 
373                 case AT:
374                     aSegment = mxText.getTextAtIndex (nIndex, nTextType);
375                     break;
376 
377                 case BEHIND:
378                     aSegment = mxText.getTextBehindIndex (nIndex, nTextType);
379                     break;
380 
381                 default:
382                     aSegment = new TextSegment();
383                     aSegment.SegmentText = new String ("unknown position "   + nWhere);
384                     aSegment.SegmentStart = nIndex;
385                     aSegment.SegmentStart = nIndex+1;
386                     break;
387             }
388         }
389         catch (IndexOutOfBoundsException aException)
390         {
391             aSegment = new TextSegment ();
392             aSegment.SegmentText = new String ("Invalid index at ") + nIndex + " : "
393                 + aException.toString();
394             aSegment.SegmentStart = nIndex;
395             aSegment.SegmentEnd = nIndex+1;
396         }
397         catch (IllegalArgumentException aException)
398         {
399             aSegment = new TextSegment ();
400             aSegment.SegmentText = new String ("Illegal argument at ") + nIndex + " : "
401                 + aException.toString();
402             aSegment.SegmentStart = nIndex;
403             aSegment.SegmentEnd = nIndex+1;
404         }
405 
406         return aSegment;
407     }
408 
409 
410     /** Add to the given node one node for every attribute of the given segment.
411     */
AddAttributeNodes( DefaultMutableTreeNode aNode, TextSegment aSegment)412     private void AddAttributeNodes (
413         DefaultMutableTreeNode aNode,
414         TextSegment aSegment)
415     {
416         try
417         {
418             PropertyValue[] aValues = mxText.getCharacterAttributes (
419                 aSegment.SegmentStart, aAttributeList);
420             for (int i=0; i<aValues.length; i++)
421                 aNode.add (new DefaultMutableTreeNode (
422                     aValues[i].Name + ": " + aValues[i].Value));
423         }
424         catch (IndexOutOfBoundsException aException)
425         {
426             aNode.add (new DefaultMutableTreeNode (
427                 "caught IndexOutOfBoundsException while retrieveing attributes"));
428         }
429     }
430 
431     private XAccessibleText mxText;
432     private JLabel
433         maTextLabel,
434         maCharacterArrayLabel,
435         maCharacterCountLabel,
436         maSelectionLabel,
437         maBoundsLabel,
438         maCaretLineNoLabel,
439         maCaretLineTextLabel,
440         maLineNoFromCaretPosLabel,
441         maLineTextFromCaretPosLabel;
442 
443     private JSpinner maCaretPositionSpinner;
444     private JTree maTree;
445     private CaretSpinnerModel maCaretSpinnerModel;
446 
447     private static String[] aAttributeList = new String[] {
448         "CharBackColor",
449         "CharColor",
450         "CharEscapement",
451         "CharHeight",
452         "CharPosture",
453         "CharStrikeout",
454         "CharUnderline",
455         "CharWeight",
456         "ParaAdjust",
457         "ParaBottomMargin",
458         "ParaFirstLineIndent",
459         "ParaLeftMargin",
460         "ParaLineSpacing",
461         "ParaRightMargin",
462         "ParaTabStops"};
463 }
464