/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.desktop.gui.virtualkeyboard; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.font.FontRenderContext; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Locale; import java.util.ResourceBundle; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.WindowConstants; import org.geogebra.common.gui.SetLabels; import org.geogebra.common.main.settings.AbstractSettings; import org.geogebra.common.main.settings.KeyboardSettings; import org.geogebra.common.main.settings.SettingListener; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Language; import org.geogebra.common.util.lang.Unicode; import org.geogebra.desktop.gui.GuiManagerD; import org.geogebra.desktop.main.AppD; import org.geogebra.desktop.main.LocalizationD; import org.geogebra.desktop.main.MyResourceBundle; import org.geogebra.desktop.util.GuiResourcesD; /** * @author Michael Borcherds (based loosely on * http://sourceforge.net/projects/virtualkey/ ) * */ @SuppressWarnings("javadoc") public class VirtualKeyboardD extends JFrame implements ActionListener, SettingListener, SetLabels { private static final long serialVersionUID = 1L; /** * List with supported languages. */ // private static Boolean Upper = false; // private JPanel jContentPane = null; // private JTextArea jTextArea = null; private JButton SpaceButton = null; private JButton DummyButton = null; private JToggleButton CapsLockButton = null; private JToggleButton AltButton = null; private JToggleButton AltGrButton = null; private JToggleButton CtrlButton = null; private JToggleButton MathButton = null; private JToggleButton NumericButton = null; private JToggleButton GreekButton = null; private JToggleButton EnglishButton = null; private String ctrlText = "Ctrl"; private String altText = "Alt"; private String altGrText = "AltG"; private String escText = "Esc"; AppD app; // max width character private final static char wideCharDefault = '@'; private char wideChar = wideCharDefault; private int buttonRows = 5; private int buttonCols = 14; private int buttonRowsNum = 4; private int buttonColsNum = 11; private double buttonSizeX, buttonSizeY; private double horizontalMultiplier = 1; private double verticalMultiplier = 1; JButton[][] Buttons = new JButton[buttonRows + 1][buttonCols]; private int windowWidth, windowHeight; private Font currentFont; private Font[] fonts = new Font[100]; private String fontName = null; private boolean shrink; private LocalizationD loc; /** * This is the default constructor * * @param app * @param windowWidth * @param windowHeight * @param opacity */ public VirtualKeyboardD(final AppD app, int windowWidth, int windowHeight, float opacity) { super(); readConf(app, null, false); this.windowWidth = windowWidth; this.windowHeight = windowHeight; this.app = app; this.setFocusableWindowState(false); this.setAlwaysOnTop(true); setFonts(); // make sure resizing the window dynamically updates the contents // doesn't seem to be needed on Java 5 Toolkit kit = Toolkit.getDefaultToolkit(); kit.setDynamicLayout(true); if (app != null) { this.loc = app.getLocalization(); initialize(); setLabels(); setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); } else { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } windowResized(); // setVisible(true); addWindowListener(new WindowListener() { @Override public void windowClosed(WindowEvent arg0) { // System.out.println("Window close event occur"); } @Override public void windowActivated(WindowEvent arg0) { // System.out.println("Window Activated"); } @Override public void windowClosing(WindowEvent arg0) { // System.out.println("Window Closing"); // if closed with the X, stop it auto-opening AppD.setVirtualKeyboardActive(false); ((GuiManagerD) app.getGuiManager()).updateMenubar(); } @Override public void windowDeactivated(WindowEvent arg0) { // System.out.println("Window Deactivated"); } @Override public void windowDeiconified(WindowEvent arg0) { // System.out.println("Window Deiconified"); } @Override public void windowIconified(WindowEvent arg0) { // System.out.println("Window Iconified"); } @Override public void windowOpened(WindowEvent arg0) { // System.out.println("Window Opened"); } }); // Event Handling getContentPane().addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { windowResized(); } }); // http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/#Setting-the-Opacity-Level-of-a-Window // AWTUtilities.setWindowOpacity // TODO: fix // force resizing of contentPane SwingUtilities.invokeLater(new Runnable() { @Override public void run() { windowResized(); } }); } private void setFonts() { String fName = app .getFontCanDisplayAwt("" + Language.getTestChar(app.getLocale().getLanguage())) .getFontName(); if (fName.equals(this.fontName)) { return; } this.fontName = fName; for (int i = 0; i < 100; i++) { fonts[i] = new Font(fName, Font.PLAIN, i + 1); } } final void windowResized() { windowWidth = getWidth(); windowHeight = getHeight(); int cpWidth = getContentPane().getWidth(); int cpHeight = getContentPane().getHeight(); if (cpWidth == 0) { cpWidth = windowWidth; } if (cpHeight == 0) { cpHeight = windowHeight; } if (getKeyboardMode() == KEYBOARD_NUMERIC) { buttonSizeX = 0.15 + cpWidth / (buttonColsNum - 0.0); buttonSizeY = 0.25 + cpHeight / (buttonRowsNum + 1.0); } else { buttonSizeX = 0.15 + (double) cpWidth / (double) (buttonCols); buttonSizeY = 0.25 + (double) cpHeight / (double) (buttonRows + 1); } // if (buttonSize < 20) buttonSize = 20; updateButtons(); KeyboardSettings kbs = app.getSettings().getKeyboard(); kbs.keyboardResized(windowWidth, windowHeight); } /** * This method initializes this keyboard */ private void initialize() { setSize(windowWidth, windowHeight); setPreferredSize(new Dimension(windowWidth, windowHeight)); populateContentPane(); } public void updateButtons() { if (getKeyboardMode() == KEYBOARD_NUMERIC && !shrink) { shrink = true; windowResized(); /* * int baseWidth = (int) * (windowWidth*buttonColsNum/(double)buttonCols); * setSize(baseWidth,windowHeight); setPreferredSize(new * Dimension(baseWidth,windowHeight)); */ } if (getKeyboardMode() != KEYBOARD_NUMERIC && shrink) { shrink = false; windowResized(); /* * int baseWidth = (int) * (windowWidth*buttonCols/(double)buttonColsNum); * setSize(baseWidth,windowHeight); setPreferredSize(new * Dimension(baseWidth,windowHeight)); */ } for (int i = 1; i <= buttonRows; i++) { for (int j = 0; j < buttonCols; j++) { updateButton(i, j); } } updateSpaceButton(); updateCapsLockButton(); updateMathButton(); updateNumericButton(); updateGreekButton(); updateEnglishButton(); updateAltButton(); updateAltGrButton(); updateCtrlButton(); } /** * This method initializes SpaceButton * * @return javax.swing.JButton */ private JButton getSpaceButton() { if (SpaceButton == null) { SpaceButton = new JButton(); SpaceButton.setRequestFocusEnabled(false); updateSpaceButton(); SpaceButton.setMargin(new Insets(0, 0, 0, 0)); SpaceButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { insertText(" "); } }); } return SpaceButton; } /* * used to find the preferred size of buttons with certain characters or * fonts */ private JButton getDummyButton() { if (DummyButton == null) { DummyButton = new JButton(wideChar + ""); DummyButton.setRequestFocusEnabled(false); DummyButton.setSize(new Dimension(10, 10)); DummyButton.setLocation(new Point(0, 0)); DummyButton.setMargin(new Insets(0, 0, 0, 0)); } return DummyButton; }// */ private void updateSpaceButton() { SpaceButton.setSize( new Dimension((int) (buttonSizeX * 5d), (int) buttonSizeY)); SpaceButton.setLocation( new Point((int) (buttonSizeX * 4d), (int) (buttonSizeY * 4d))); SpaceButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); } private void updateCapsLockButton() { CapsLockButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); CapsLockButton.setLocation( new Point((int) (buttonSizeX / 2d), (int) (buttonSizeY * 4d))); CapsLockButton.setFont(getFont((int) (minButtonSize()), false)); setColor(CapsLockButton); CapsLockButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); } void updateCtrlButton() { CtrlButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); CtrlButton.setLocation(new Point((int) (buttonSizeX * 3d / 2d), (int) (buttonSizeY * 4d))); CtrlButton.setFont(getFont((int) (minButtonSize() / 2), false)); CtrlButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); setColor(CtrlButton); } void updateAltButton() { AltButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); AltButton.setLocation(new Point((int) (buttonSizeX * 5d / 2d), (int) (buttonSizeY * 4d))); AltButton.setFont(getFont((int) (minButtonSize() / 2), false)); setColor(AltButton); AltButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); if (sbAlt != null) { sbAlt.setLength(0); } } void updateAltGrButton() { AltGrButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); AltGrButton.setLocation( new Point((int) (buttonSizeX * 9), (int) (buttonSizeY * 4d))); AltGrButton.setFont(getFont((int) (minButtonSize() / 2), false)); AltGrButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); setColor(AltGrButton); } private void updateMathButton() { MathButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); MathButton.setLocation( new Point((int) (buttonSizeX * 10), (int) (buttonSizeY * 4d))); MathButton.setFont(getFont((int) (minButtonSize()), false)); MathButton.setVisible(getKeyboardMode() != KEYBOARD_NUMERIC); setColor(MathButton); } private void updateNumericButton() { NumericButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); if (getKeyboardMode() != KEYBOARD_NUMERIC) { NumericButton.setLocation(new Point((int) (buttonSizeX * 13), (int) (buttonSizeY * 4d))); } else { NumericButton.setLocation(new Point((int) (buttonSizeX * 10), (int) (buttonSizeY * 2d))); } NumericButton.setFont(getFont((int) (minButtonSize()), false)); setColor(NumericButton); } private static void setColor(JToggleButton tb) { if (tb.isSelected()) { tb.setBackground(Color.cyan); } else { tb.setBackground(null); } } private void updateGreekButton() { GreekButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); if (getKeyboardMode() != KEYBOARD_NUMERIC) { GreekButton.setLocation(new Point((int) (buttonSizeX * 12), (int) (buttonSizeY * 4d))); } else { GreekButton.setLocation(new Point((int) (buttonSizeX * 10), (int) (buttonSizeY * 1d))); } GreekButton.setFont(getFont((int) (minButtonSize()), false)); setColor(GreekButton); } private void updateEnglishButton() { EnglishButton .setSize(new Dimension((int) (buttonSizeX), (int) buttonSizeY)); if (getKeyboardMode() != KEYBOARD_NUMERIC) { EnglishButton.setLocation(new Point((int) (buttonSizeX * 11), (int) (buttonSizeY * 4d))); } else { EnglishButton.setLocation(new Point((int) (buttonSizeX * 10), (int) (buttonSizeY * 0d))); } EnglishButton.setFont(getFont((int) (minButtonSize()), false)); EnglishButton.setVisible(true); setColor(EnglishButton); } private double minButtonSize() { double ret = Math.min(buttonSizeX * horizontalMultiplier, buttonSizeY * verticalMultiplier); return (ret == 0) ? 1 : ret; } private JToggleButton getCapsLockButton() { if (CapsLockButton == null) { CapsLockButton = new JToggleButton("\u21e7"); updateCapsLockButton(); CapsLockButton.setMargin(new Insets(0, 0, 0, 0)); CapsLockButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateButtons(); } }); } return CapsLockButton; } private JToggleButton getAltButton() { if (AltButton == null) { AltButton = new JToggleButton(altText); updateAltButton(); AltButton.setMargin(new Insets(0, 0, 0, 0)); AltButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // altPressed = !altPressed; updateAltButton(); } }); } return AltButton; } private JToggleButton getAltGrButton() { if (AltGrButton == null) { AltGrButton = new JToggleButton(altGrText); updateAltGrButton(); AltGrButton.setMargin(new Insets(0, 0, 0, 0)); AltGrButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateButtons(); updateAltGrButton(); } }); } return AltGrButton; } private JToggleButton getCtrlButton() { if (CtrlButton == null) { CtrlButton = new JToggleButton(ctrlText); updateCtrlButton(); CtrlButton.setMargin(new Insets(0, 0, 0, 0)); CtrlButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateCtrlButton(); } }); } return CtrlButton; } private JToggleButton getMathButton() { if (MathButton == null) { MathButton = new JToggleButton("\u222b"); updateMathButton(); MathButton.setMargin(new Insets(0, 0, 0, 0)); MathButton.setToolTipText(loc.getMenu("Keyboard.Math")); MathButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getGreekButton().setSelected(false); getEnglishButton().setSelected(false); if (getKeyboardMode() != KEYBOARD_MATH) { setMode(KEYBOARD_MATH, null); } else { setMode(KEYBOARD_NORMAL, null); } } }); } return MathButton; } public void toggleNumeric(boolean numeric) { setMode(numeric ? KEYBOARD_NUMERIC : KEYBOARD_NORMAL, null); } private JToggleButton getNumericButton() { if (NumericButton == null) { NumericButton = new JToggleButton(); NumericButton .setIcon(app.getScaledIcon(GuiResourcesD.CAS_KEYBOARD)); NumericButton.setToolTipText(loc.getMenu("Keyboard.Numeric")); updateNumericButton(); NumericButton.setMargin(new Insets(0, 0, 0, 0)); NumericButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getGreekButton().setSelected(false); getEnglishButton().setSelected(false); if (getKeyboardMode() != KEYBOARD_NUMERIC) { setMode(KEYBOARD_NUMERIC, null); } else { setMode(KEYBOARD_NORMAL, null); } } }); } return NumericButton; } boolean greek() { return getGreekButton().isSelected(); } JToggleButton getGreekButton() { if (GreekButton == null) { GreekButton = new JToggleButton("\u03b1"); updateGreekButton(); GreekButton.setMargin(new Insets(0, 0, 0, 0)); GreekButton.setToolTipText(loc.getMenu("Keyboard.Greek")); GreekButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setMode(KEYBOARD_NORMAL, null); if (greek()) { readConf(app, new Locale("el"), false); } getEnglishButton().setSelected(false); updateButtons(); } }); } return GreekButton; } boolean english() { return getEnglishButton().isSelected(); } JToggleButton getEnglishButton() { if (EnglishButton == null) { EnglishButton = new JToggleButton("a"); updateEnglishButton(); EnglishButton.setToolTipText(loc.getMenu("Keyboard.Standard")); EnglishButton.setMargin(new Insets(0, 0, 0, 0)); EnglishButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setMode(KEYBOARD_NORMAL, null); if (english()) { readConf(app, new Locale("en"), false); } getGreekButton().setSelected(false); updateButtons(); } }); } return EnglishButton; } /** * This method initializes jContentPane */ private void populateContentPane() { setLayout(null); for (int i = 1; i <= buttonRows; i++) { for (int j = 0; j < buttonCols; j++) { add(getButton(i, j), null); } } add(getSpaceButton(), null); add(getCapsLockButton(), null); add(getMathButton(), null); add(getNumericButton(), null); add(getGreekButton(), null); add(getEnglishButton(), null); add(getAltButton(), null); add(getAltGrButton(), null); add(getCtrlButton(), null); pack(); } public static final char KEYBOARD_NORMAL = ' '; public static final char KEYBOARD_MATH = 'M'; public static final char KEYBOARD_NUMERIC = 'N'; // public static final char KEYBOARD_ALTGR = 'Q'; public static final char KEYBOARD_ACUTE = 'A'; public static final char KEYBOARD_GRAVE = 'G'; public static final char KEYBOARD_UMLAUT = 'U'; public static final char KEYBOARD_CEDILLA = 'c'; public static final char KEYBOARD_CARON = 'v'; public static final char KEYBOARD_CIRCUMFLEX = 'C'; public static final char KEYBOARD_BREVE = 'B'; public static final char KEYBOARD_TILDE = 'T'; public static final char KEYBOARD_OGONEK = 'O'; public static final char KEYBOARD_DOT_ABOVE = 'D'; public static final char KEYBOARD_RING_ABOVE = 'R'; public static final char KEYBOARD_DIALYTIKA_TONOS = 'd'; public static final char KEYBOARD_DOUBLE_ACUTE = 'a'; public static final char KEYBOARD_SOLIDUS = '/'; private char KEYBOARD_MODE = KEYBOARD_NORMAL; /** * This method adds a char to the text-field */ void insertText(String str) { String addchar = str; if (addchar.length() == 1) { switch (addchar.charAt(0)) { case '\u00b4': // acute setMode(KEYBOARD_ACUTE, kbLocale); return; case '\u0338': // solidus (/) setMode(KEYBOARD_SOLIDUS, kbLocale); return; // case '\u0060': // grave case '\u0300': // combining grave setMode(KEYBOARD_GRAVE, kbLocale); return; case '\u02d8': // breve setMode(KEYBOARD_BREVE, kbLocale); return; case '\u0303': // tilde setMode(KEYBOARD_TILDE, kbLocale); return; case '\u0302': // circumflex setMode(KEYBOARD_CIRCUMFLEX, kbLocale); return; case '\u0385': // dialytika tonos setMode(KEYBOARD_DIALYTIKA_TONOS, kbLocale); return; case '\u00b8': // cedilla setMode(KEYBOARD_CEDILLA, kbLocale); return; case '\u00a8': // umlaut setMode(KEYBOARD_UMLAUT, kbLocale); return; case '\u02c7': // caron setMode(KEYBOARD_CARON, kbLocale); return; case '\u02d9': // dot above setMode(KEYBOARD_DOT_ABOVE, kbLocale); return; case '\u02db': // Ogonek setMode(KEYBOARD_OGONEK, kbLocale); return; case '\u02da': // ring above setMode(KEYBOARD_RING_ABOVE, kbLocale); return; case '\u02dd': // double acute setMode(KEYBOARD_DOUBLE_ACUTE, kbLocale); return; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (AltButton.isSelected()) { StringBuilder asb = getAltStringBuilder(); asb.append(addchar); AltButton.setBackground(Color.orange); if (asb.length() < 4) { return; } // convert string to Unicode char char c = (char) Integer.parseInt(asb.toString()); // release alt AltButton.setSelected(false); updateAltButton(); // type Unicode char ((GuiManagerD) app.getGuiManager()) .insertStringIntoTextfield(c + "", false, false, false); asb.setLength(0); return; } // else pass on as normal } } if ("<enter>".equals(addchar)) { addchar = "\n"; } else if ("<E>".equals(addchar)) { addchar = "E"; } else if ("<multiply>".equals(addchar)) { addchar = "*"; } else if ("<divide>".equals(addchar)) { addchar = "/"; } else if ("<minus>".equals(addchar)) { addchar = "-"; } if (app != null) { ((GuiManagerD) app.getGuiManager()).insertStringIntoTextfield( addchar, getAltButton().isSelected(), getCtrlButton().isSelected(), getCapsLockButton().isSelected()); } else { getKeyboard().doType(getAltButton().isSelected(), getCtrlButton().isSelected(), getCapsLockButton().isSelected(), addchar); } // no special keys pressed, reset to normal (except eg Greek) if (getKeyboardMode() != KEYBOARD_NUMERIC) { setMode(KEYBOARD_NORMAL, kbLocale); } } StringBuilder sbAlt; private StringBuilder getAltStringBuilder() { if (sbAlt == null) { sbAlt = new StringBuilder(); } return sbAlt; } void setMode(char mode, Locale loc) { // loc==null -> restore language (eg if greek selected before) readConf(app, loc, false); if (getKeyboardMode() == mode) { setKEYBOARD_MODE(KEYBOARD_NORMAL); } else { // reset first setKEYBOARD_MODE(KEYBOARD_NORMAL); updateButtons(); // new mode setKEYBOARD_MODE(mode); } if (getKeyboardMode() != KEYBOARD_MATH) { getMathButton().setSelected(false); } if (getKeyboardMode() == KEYBOARD_MATH) { getAltGrButton().setSelected(false); } updateButtons(); } private boolean Upper() { return getCapsLockButton().isSelected(); } /** * This method adds a char to the text-field * */ private void insertKeyText(KeyboardKeys Keys) { if (Upper()) { insertText(Keys.getUpperCase()); } else { insertText(Keys.getLowerCase()); } } private StringBuilder sb = new StringBuilder(); private KeyboardKeys getKey(int i, int j) { sb.setLength(0); sb.append('B'); if (i < 10) { sb.append('0'); // pad from "1" to "01" } sb.append(i + ""); if (j < 10) { sb.append('0'); // pad from "1" to "01" } sb.append(j + ""); KeyboardKeys ret1 = myKeys.get(sb.toString()); if (ret1 == null) { Log.debug("KB Error: " + sb.toString()); } sb.append(getKeyboardMode()); // append 'A' for acute , ' ' for default // etc KeyboardKeys ret2 = myKeys.get(sb.toString()); // check for AltGr (Q) code if no accent etc available if (ret2 == null && getAltGrButton().isSelected()) { sb.setLength(sb.length() - 1); // remove MODE sb.append("Q"); ret2 = myKeys.get(sb.toString()); } return ret2 != null ? ret2 : ret1; } private JButton getButton(final int i, final int j) { if (Buttons[i][j] == null) { KeyboardKeys thisKeys = getKey(i, j); Buttons[i][j] = new JButton(); updateButton(i, j); Insets Inset = new Insets(0, 0, 0, 0); Buttons[i][j].setMargin(Inset); String text = Upper() ? thisKeys.getUpperCase() : thisKeys.getLowerCase(); Buttons[i][j].setText(processSpecialKeys(text)); Buttons[i][j].addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { String txt = Buttons[i][j].getText(); if ("\u2190".equals(txt)) { startAutoRepeat("<left>"); } else if ("\u2191".equals(txt)) { startAutoRepeat("<up>"); } else if ("\u2192".equals(txt)) { startAutoRepeat("<right>"); } else if ("\u2193".equals(txt)) { startAutoRepeat("<down>"); } else if ("\u21a4".equals(txt)) { startAutoRepeat("<backspace>"); } // Application.debug(Buttons[i][j].getText()); } @Override public void mouseReleased(MouseEvent e) { stopAutoRepeat(); } @Override public void mouseExited(MouseEvent e) { stopAutoRepeat(); } }); Buttons[i][j].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { buttonPressed(i, j); } }); } return Buttons[i][j]; } void buttonPressed(int i, int j) { // don't insert if timer running // (done in timer on auto-repeat) if (timer == null || !timer.isRunning()) { insertKeyText(getKey(i, j)); } boolean doUpdateButtons = false; // reset buttons on a keypress (one-shot mode) if (getCapsLockButton().isSelected()) { getCapsLockButton().setSelected(false); doUpdateButtons = true; } if (getAltGrButton().isSelected()) { getAltGrButton().setSelected(false); doUpdateButtons = true; } if (getCtrlButton().isSelected()) { getCtrlButton().setSelected(false); doUpdateButtons = true; } if (doUpdateButtons) { updateButtons(); } } private String processSpecialKeys(String text) { // check first for speed if (!text.startsWith("<")) { return text; } if ("<enter>".equals(text)) { return unicodeString('\u21b2', ""); } if ("<backspace>".equals(text)) { return "\u21a4"; } if ("<escape>".equals(text)) { return (app == null) ? escText : loc.getMenu("Esc"); } if ("<left>".equals(text)) { return "\u2190"; } if ("<up>".equals(text)) { return "\u2191"; } if ("<right>".equals(text)) { return "\u2192"; } if ("<down>".equals(text)) { return "\u2193"; } if ("<E>".equals(text)) { return "\u00D710\u207F"; // *10^n } if ("<multiply>".equals(text)) { return Unicode.MULTIPLY + ""; } if ("<divide>".equals(text)) { return Unicode.DIVIDE; } if ("<minus>".equals(text)) { return Unicode.MINUS + ""; } return text; } private String unicodeString(char c, String alternative) { if (getCurrentFont().canDisplay(c)) { return c + ""; } return alternative; } HashMap<Character, Boolean> characterIsTooWide = new HashMap<Character, Boolean>( 200); private void updateButton(int i, int j) { KeyboardKeys k = getKey(i, j); if (Upper()) { Buttons[i][j].setText(processSpecialKeys(k.getUpperCase())); } else { Buttons[i][j].setText(processSpecialKeys(k.getLowerCase())); } if (getKeyboardMode() == KEYBOARD_NUMERIC && (i > 5 || j > 6 || (i == 5 && j > 4))) { Buttons[i][j].setVisible(false); } else { Buttons[i][j].setVisible(true); } setTooltip(i, j); // skip a row (for spacebar etc) int ii = (i == 5 && getKeyboardMode() != KEYBOARD_NUMERIC) ? 6 : i; int height = (int) buttonSizeY; int width = (int) buttonSizeX; int xOffset = 0; if (getKeyboardMode() == KEYBOARD_NUMERIC) { xOffset = (j > 0 ? 1 : 0) + (j > 1 && i < 5 ? 1 : 0) + (j > 2 ? 1 : 0) + (j > 2 && i < 5 ? 1 : 0) + (j > 4 ? 1 : 0) + (i == 5 && j > 0 ? 7 : 0); } // enter key: double height if (i == 3 && j == 13) { Buttons[i][j].setVisible(getKeyboardMode() == KEYBOARD_MATH); } if (i == 2 && j == 13 && getKeyboardMode() != KEYBOARD_MATH) { height *= 2; } if (i < 5 && j < 3 && getKeyboardMode() == KEYBOARD_NUMERIC) { width *= 1.5; } if (i == 5 && j == 0 && getKeyboardMode() == KEYBOARD_NUMERIC) { width *= 4.5; } Buttons[i][j].setBounds( new Rectangle((int) (0.5 + buttonSizeX * (j + 0.5 * xOffset)), (int) (0.5 + buttonSizeY * (ii - 1)), width, height)); String text = Buttons[i][j].getText(); int len = text.length(); if (len == 0) { len = 1; text = " "; } if (len == 1) { // make sure extra-wide characters fit (eg <=> \u21d4 ) FontRenderContext frc = new FontRenderContext(null, true, true); double wideCharWidth = getCurrentFont() .getStringBounds(wideChar + "", frc).getWidth(); double charWidth = getCurrentFont().getStringBounds(text, frc) .getWidth(); boolean oversize = charWidth > wideCharWidth; if (oversize) { Buttons[i][j].setFont( getFont((int) minButtonSize() * 10 / 12, false)); } else { Buttons[i][j].setFont(getFont((int) minButtonSize(), true)); } } else { // make sure "Esc" fits FontMetrics fm = getFontMetrics(getCurrentFont()); int width2 = fm.stringWidth(Buttons[i][j].getText()); // wide arrow // <=> int w2 = fm.stringWidth(wideChar + ""); if (i == 4 && j == 2 && getKeyboardMode() == KEYBOARD_NUMERIC) { Buttons[i][j].setFont(getFont( (int) (minButtonSize() * 1.5 * w2 / width2), false)); } else { Buttons[i][j].setFont( getFont((int) (minButtonSize() * w2 / width2), false)); } } } private void setTooltip(int i, int j) { String text = null; if (getKeyboardMode() == KEYBOARD_NUMERIC) { String src = Buttons[i][j].getText(); if (":=".equals(src)) { text = "Assignment"; } else if ("$".equals(src)) { text = "DynamicReference"; } else if ("#".equals(src)) { text = "StaticReference"; } else if ((Unicode.VECTOR_PRODUCT + "").equals(src)) { text = "VectorProduct"; } if (text != null) { text = loc.getMenu("Symbol." + text); } } Buttons[i][j].setToolTipText(text); } private Font getCurrentFont() { if (currentFont != null) { return currentFont; } return getFont((int) (minButtonSize()), true); } private HashMap<Integer, Font> fontsHash = new HashMap<Integer, Font>(30); private Font getFont(int size, boolean setFont) { Integer Size = Integer.valueOf(size); Font ret = fontsHash.get(Size); // all OK, return if (ret != null) { return ret; } int maxSize = 100; int minSize = 1; // interval bisection method to find desired fontsize while (minSize != maxSize - 1) { // Application.debug(minSize+" "+maxSize); // better than (low+high)/2 for positive numbers int midSize = (minSize + maxSize) >>> 1; getDummyButton().setFont(fonts[midSize]); getDummyButton().setText(wideChar + ""); Dimension buttonSize = DummyButton.getPreferredSize(); int wideCharSize = buttonSize.width; if (wideCharSize < size) { minSize = midSize; } else { maxSize = midSize; } } /* * fallback for Mac OS getPreferredSize() is returning a minimum of * width = 75 height = 29 so we just choose size / 2 for the fontSize */ if (minSize < 3) { minSize = size / 2; } if (setFont) { currentFont = fonts[minSize]; } fontsHash.put(Size, fonts[minSize]); // Application.debug("KB: storing "+size+" "+minSize); return fonts[minSize]; } private Hashtable<String, KeyboardKeys> myKeys = new Hashtable<String, KeyboardKeys>(); private Locale kbLocale = null; public void setKbLocale(Locale loc) { readConf(app, loc, false); doSetLabels(); } void readConf(AppD appD, Locale loc0, boolean math) { ResourceBundle rbKeyboard; Locale locale; if (appD != null) { String locName = appD.getSettings().getKeyboard() .getKeyboardLocale(); if (locName == null) { locale = appD.getLocale(); } else { locale = new Locale(locName); } } else { locale = getLocale(); } kbLocale = locale; if (math) { rbKeyboard = MyResourceBundle.createBundle( "/org/geogebra/desktop/gui/virtualkeyboard/keyboardMath", locale); } else { if (loc0 == null) { rbKeyboard = MyResourceBundle.createBundle( "/org/geogebra/desktop/gui/virtualkeyboard/keyboard", locale); } else { rbKeyboard = MyResourceBundle.createBundle( "/org/geogebra/desktop/gui/virtualkeyboard/keyboard", loc0); kbLocale = loc0; } } Enumeration<String> keys = rbKeyboard.getKeys(); while (keys.hasMoreElements()) { String keyU = keys.nextElement(); if (keyU.endsWith("U")) { KeyboardKeys keyItem = new KeyboardKeys(); String key = keyU.substring(0, keyU.length() - 1); String valueU = rbKeyboard.getString(keyU); String valueL = rbKeyboard.getString(key + "L"); keyItem.setLowerCase(valueL); keyItem.setUpperCase(valueU); myKeys.put(key, keyItem); } } } /* * called when eg language changed */ @Override public void setLabels() { setFonts(); setTitle((app == null) ? "Virtual Keyboard" : loc.getPlain("VirtualKeyboard")); readConf(app, null, false); doSetLabels(); } private void doSetLabels() { if (kbLocale.getLanguage().equals("ml")) { wideChar = '\u0d4c'; // widest Malayalan char } else if (kbLocale.getLanguage().equals("ar")) { wideChar = '\u0636'; // widest Arabic char } else { wideChar = wideCharDefault; } if (fontsHash != null) { fontsHash.clear(); } if (app != null) { getCtrlButton().setText(loc.getMenu("Ctrl")); getAltButton().setText(loc.getMenu("Alt")); getAltGrButton().setText(loc.getMenu("AltGr")); updateCtrlButton(); updateAltButton(); updateAltGrButton(); } getAltButton().setSelected(false); getAltGrButton().setSelected(false); getMathButton().setSelected(false); getNumericButton().setSelected(false); getGreekButton().setSelected(false); getEnglishButton().setSelected(false); getCtrlButton().setSelected(false); getCapsLockButton().setSelected(false); setKEYBOARD_MODE(KEYBOARD_NORMAL); updateButtons(); } public WindowsUnicodeKeyboard getKeyboard() { WindowsUnicodeKeyboard kb = null; try { kb = new WindowsUnicodeKeyboard(); } catch (Exception e) { e.printStackTrace(); } return kb; } private Timer timer; private String timerInsertStr = ""; final void startAutoRepeat(String str) { if (timer == null) { timer = new Timer(1000, this); } timer.stop(); timer.setDelay(1000); timer.start(); timer.setDelay(200); // long first pause then quicker repeat timerInsertStr = str; insertAutoRepeatString(); } private void insertAutoRepeatString() { ((GuiManagerD) app.getGuiManager()).insertStringIntoTextfield( timerInsertStr, getAltButton().isSelected(), getCtrlButton().isSelected(), getCapsLockButton().isSelected()); } final void stopAutoRepeat() { if (timer != null) { timer.stop(); } } @Override public void actionPerformed(ActionEvent e) { // timer event insertAutoRepeatString(); } public void setWindowWidth(int windowWidth) { this.windowWidth = windowWidth; setSize(windowWidth, windowHeight); } public void setWindowHeight(int windowHeight) { this.windowHeight = windowHeight; setSize(windowWidth, windowHeight); } @Override public void settingsChanged(AbstractSettings settings) { KeyboardSettings kbs = (KeyboardSettings) settings; setWindowHeight(kbs.getKeyboardHeight()); setWindowWidth(kbs.getKeyboardWidth()); Locale newLocale = kbs.getKeyboardLocale() == null ? app.getLocale() : new Locale(kbs.getKeyboardLocale()); if (!newLocale.equals(kbLocale)) { setKbLocale(newLocale); } } public char getKeyboardMode() { return KEYBOARD_MODE; } public void setKEYBOARD_MODE(char kEYBOARD_MODE) { KEYBOARD_MODE = kEYBOARD_MODE; } }