/* * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle or the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * BidiTextComponentDemo.java requires one additional file: * DocumentSizeFilter.java */ import java.awt.*; import java.awt.event.*; import java.awt.font.TextAttribute; import java.util.HashMap; import java.util.Locale; import javax.swing.*; import javax.swing.text.*; import javax.swing.event.*; import javax.swing.undo.*; public class BidiTextComponentDemo extends JFrame { JTextPane textPane; AbstractDocument doc; static final int MAX_CHARACTERS = 500; JTextArea changeLog; String newline = "\n"; HashMap actions; // undo helpers protected UndoAction undoAction; protected RedoAction redoAction; protected UndoManager undo = new UndoManager(); public BidiTextComponentDemo() { super("BidiTextComponentDemo"); // Create the text pane and configure it. textPane = new JTextPane(); // The following statement specifies that the run direction of // the text contained in the JTextPane object is // right-to-left. // textPane.getDocument().putProperty(TextAttribute.RUN_DIRECTION,TextAttribute.RUN_DIRECTION_RTL); // The following statements specify that the component orientation // of the JTextPane object is based on the ar-SA locale. Consequently, // these statements also set the run direction of the text contained // in the JTextPane object right-to-left. // Locale arabicSaudiArabia = new Locale.Builder().setLanguage("ar").setRegion("SA").build(); // textPane.setComponentOrientation(ComponentOrientation.getOrientation(arabicSaudiArabia)); textPane.setCaretPosition(0); textPane.setMargin(new Insets(5, 5, 5, 5)); StyledDocument styledDoc = textPane.getStyledDocument(); if (styledDoc instanceof AbstractDocument) { doc = (AbstractDocument) styledDoc; doc.setDocumentFilter(new DocumentSizeFilter(MAX_CHARACTERS)); } else { System.err.println("Text pane's document isn't an AbstractDocument!"); System.exit(-1); } JScrollPane scrollPane = new JScrollPane(textPane); scrollPane.setPreferredSize(new Dimension(200, 100)); // Create the text area for the status log and configure it. changeLog = new JTextArea(5, 30); changeLog.setEditable(false); JScrollPane scrollPaneForLog = new JScrollPane(changeLog); // Create a split pane for the change log and the text area. JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, scrollPaneForLog); splitPane.setOneTouchExpandable(true); // Create the status area. JPanel statusPane = new JPanel(new GridLayout(1, 1)); CaretListenerLabel caretListenerLabel = new CaretListenerLabel( "Caret Status"); statusPane.add(caretListenerLabel); // Add the components. getContentPane().add(splitPane, BorderLayout.CENTER); getContentPane().add(statusPane, BorderLayout.PAGE_END); // Set up the menu bar. actions = createActionTable(textPane); JMenu editMenu = createEditMenu(); JMenu styleMenu = createStyleMenu(); JMenuBar mb = new JMenuBar(); mb.add(editMenu); mb.add(styleMenu); setJMenuBar(mb); // Add some key bindings. addBindings(); // Put the initial text into the text pane. initDocument(); textPane.setCaretPosition(0); // Start watching for undoable edits and caret changes. doc.addUndoableEditListener(new MyUndoableEditListener()); textPane.addCaretListener(caretListenerLabel); doc.addDocumentListener(new MyDocumentListener()); } // This listens for and reports caret movements. protected class CaretListenerLabel extends JLabel implements CaretListener { public CaretListenerLabel(String label) { super(label); } // Might not be invoked from the event dispatch thread. public void caretUpdate(CaretEvent e) { displaySelectionInfo(e.getDot(), e.getMark()); } // This method can be invoked from any thread. It // invokes the setText and modelToView methods, which // must run on the event dispatch thread. We use // invokeLater to schedule the code for execution // on the event dispatch thread. protected void displaySelectionInfo(final int dot, final int mark) { SwingUtilities.invokeLater(new Runnable() { public void run() { if (dot == mark) { // no selection try { Rectangle caretCoords = textPane.modelToView(dot); // Convert it to view coordinates. setText("caret: text position: " + dot + ", view location = [" + caretCoords.x + ", " + caretCoords.y + "]" + newline); } catch (BadLocationException ble) { setText("caret: text position: " + dot + newline); } } else if (dot < mark) { setText("selection from: " + dot + " to " + mark + newline); } else { setText("selection from: " + mark + " to " + dot + newline); } } }); } } // This one listens for edits that can be undone. protected class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { // Remember the edit and update the menus. undo.addEdit(e.getEdit()); undoAction.updateUndoState(); redoAction.updateRedoState(); } } // And this one listens for any changes to the document. protected class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { displayEditInfo(e); } public void removeUpdate(DocumentEvent e) { displayEditInfo(e); } public void changedUpdate(DocumentEvent e) { displayEditInfo(e); } private void displayEditInfo(DocumentEvent e) { Document document = e.getDocument(); int changeLength = e.getLength(); changeLog.append(e.getType().toString() + ": " + changeLength + " character" + ((changeLength == 1) ? ". " : "s. ") + " Text length = " + document.getLength() + "." + newline); } } // Add a couple of emacs key bindings for navigation. protected void addBindings() { InputMap inputMap = textPane.getInputMap(); // Ctrl-b to go backward one character KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); inputMap.put(key, DefaultEditorKit.backwardAction); // Ctrl-f to go forward one character key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK); inputMap.put(key, DefaultEditorKit.forwardAction); // Ctrl-p to go up one line key = KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK); inputMap.put(key, DefaultEditorKit.upAction); // Ctrl-n to go down one line key = KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK); inputMap.put(key, DefaultEditorKit.downAction); } // Create the edit menu. protected JMenu createEditMenu() { JMenu menu = new JMenu("Edit"); // Undo and redo are actions of our own creation. undoAction = new UndoAction(); menu.add(undoAction); redoAction = new RedoAction(); menu.add(redoAction); menu.addSeparator(); // These actions come from the default editor kit. // Get the ones we want and stick them in the menu. menu.add(getActionByName(DefaultEditorKit.cutAction)); menu.add(getActionByName(DefaultEditorKit.copyAction)); menu.add(getActionByName(DefaultEditorKit.pasteAction)); menu.addSeparator(); menu.add(getActionByName(DefaultEditorKit.selectAllAction)); return menu; } // Create the style menu. protected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); action = new StyledEditorKit.ItalicAction(); action.putValue(Action.NAME, "Italic"); menu.add(action); action = new StyledEditorKit.UnderlineAction(); action.putValue(Action.NAME, "Underline"); menu.add(action); menu.addSeparator(); menu.add(new StyledEditorKit.FontSizeAction("12", 12)); menu.add(new StyledEditorKit.FontSizeAction("14", 14)); menu.add(new StyledEditorKit.FontSizeAction("18", 18)); menu.addSeparator(); menu.add(new StyledEditorKit.FontFamilyAction("Serif", "Serif")); menu.add(new StyledEditorKit.FontFamilyAction("SansSerif", "SansSerif")); menu.addSeparator(); menu.add(new StyledEditorKit.ForegroundAction("Red", Color.red)); menu.add(new StyledEditorKit.ForegroundAction("Green", Color.green)); menu.add(new StyledEditorKit.ForegroundAction("Blue", Color.blue)); menu.add(new StyledEditorKit.ForegroundAction("Black", Color.black)); return menu; } protected void initDocument() { String bidiText = "Hello World in Arabic is " + "\u0645\u0631\u062D\u0628\u0627\u0020\u0627\u0644\u0639\u0627\u0644\u0645" + "."; String initString[] = { bidiText }; SimpleAttributeSet[] attrs = initAttributes(initString.length); try { for (int i = 0; i < initString.length; i++) { doc.insertString(doc.getLength(), initString[i] + newline, attrs[i]); } } catch (BadLocationException ble) { System.err.println("Couldn't insert initial text."); } } protected SimpleAttributeSet[] initAttributes(int length) { SimpleAttributeSet[] attrs = new SimpleAttributeSet[length]; attrs[0] = new SimpleAttributeSet(); StyleConstants.setFontSize(attrs[0], 18); StyleConstants.setFontFamily(attrs[0], "Serif"); return attrs; } // The following two methods allow us to find an // action provided by the editor kit by its name. private HashMap createActionTable(JTextComponent textComponent) { HashMap actions = new HashMap(); Action[] actionsArray = textComponent.getActions(); for (int i = 0; i < actionsArray.length; i++) { Action a = actionsArray[i]; actions.put(a.getValue(Action.NAME), a); } return actions; } private Action getActionByName(String name) { return actions.get(name); } class UndoAction extends AbstractAction { public UndoAction() { super("Undo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.undo(); } catch (CannotUndoException ex) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } updateUndoState(); redoAction.updateRedoState(); } protected void updateUndoState() { if (undo.canUndo()) { setEnabled(true); putValue(Action.NAME, undo.getUndoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Undo"); } } } class RedoAction extends AbstractAction { public RedoAction() { super("Redo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } updateRedoState(); undoAction.updateUndoState(); } protected void updateRedoState() { if (undo.canRedo()) { setEnabled(true); putValue(Action.NAME, undo.getRedoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Redo"); } } } /** * Create the GUI and show it. For thread safety, this method should be * invoked from the event dispatch thread. */ private static void createAndShowGUI() { // Create and set up the window. final BidiTextComponentDemo frame = new BidiTextComponentDemo(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Display the window. frame.pack(); frame.setVisible(true); } // The standard main method. public static void main(String[] args) { // Schedule a job for the event dispatch thread: // creating and showing this application's GUI. SwingUtilities.invokeLater(new Runnable() { public void run() { // Turn off metal's use of bold fonts UIManager.put("swing.boldMetal", Boolean.FALSE); createAndShowGUI(); } }); } }