    rev 4839 : RT-2529: Locale specific runtime errors (Turkish)

       1 /*
       2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package com.sun.javafx.scene.control.skin;
      27
      28 import javafx.animation.Animation;
      29 import javafx.animation.Interpolator;
      30 import javafx.animation.KeyFrame;
      31 import javafx.animation.KeyValue;
      32 import javafx.animation.Timeline;
      33 import javafx.application.Platform;
      34 import javafx.beans.InvalidationListener;
      35 import javafx.beans.Observable;
      36 import javafx.beans.property.DoubleProperty;
      37 import javafx.beans.property.SimpleDoubleProperty;
      38 import javafx.event.ActionEvent;
      39 import javafx.event.EventHandler;
      40 import javafx.event.EventTarget;
      41 import javafx.geometry.HPos;
      42 import javafx.geometry.Point2D;
      43 import javafx.geometry.Rectangle2D;
      44 import javafx.geometry.VPos;
      45 import javafx.scene.Node;
      46 import javafx.scene.Scene;
      47 import javafx.scene.input.InputEvent;
      48 import javafx.scene.input.KeyCode;
      49 import javafx.scene.input.KeyEvent;
      50 import javafx.scene.input.MouseEvent;
      51 import javafx.scene.layout.Region;
      52 import javafx.scene.text.Text;
      53 import javafx.stage.Popup;
      54 import javafx.util.Duration;
      55 import java.io.BufferedReader;
      56 import java.io.InputStream;
      57 import java.io.InputStreamReader;
      58 import java.util.ArrayList;
      59 import java.util.Collections;
      60 import java.util.List;
      61 import java.util.Locale;
      62 import com.sun.javafx.css.StyleManager;
      63 import com.sun.javafx.scene.control.behavior.BehaviorBase;
      64 import static javafx.scene.input.MouseEvent.MOUSE_PRESSED;
      65 import static javafx.scene.input.TouchEvent.TOUCH_PRESSED;
      66 import static javafx.scene.layout.Region.USE_PREF_SIZE;
      67
      68
      69 public class FXVKSkin extends BehaviorSkinBase<FXVK, BehaviorBase<FXVK>> {
      70
      71     private static final int GAP = 6;
      72
      73     private List<List<Key>> board;
      74     private int numCols;
      75
      76     private boolean capsDown = false;
      77     private boolean shiftDown = false;
      78     private boolean isSymbol = false;
      79     long lastTime = -1L;
      80
      81     void clearShift() {
      82         if (shiftDown && !capsDown) {
      83             shiftDown = false;
      84             updateKeys();
      85         }
      86         lastTime = -1L;
      87     }
      88
      89     void pressShift() {
      90         long time = System.currentTimeMillis();
      91
      92         //potential for a shift lock
      93         if (shiftDown && !capsDown) {
      94             if (lastTime > 0L && time - lastTime < 400L) {
      95                 //set caps lock
      96                 shiftDown = false;
      97                 capsDown =  true;
      98             } else {
      99                 //set normal
     100                 shiftDown = false;
     101                 capsDown =  false;
     102             }
     103         } else if (!shiftDown && !capsDown) {
     104             // set shift
     105             shiftDown=true;
     106         } else {
     107             //set to normal
     108             shiftDown = false;
     109             capsDown =  false;
     110         }
     111
     112         updateKeys();
     113         lastTime = time;
     114     }
     115
     116     void clearSymbolABC() {
     117         isSymbol = false;
     118         updateKeys();
     119     }
     120
     121     void pressSymbolABC() {
     122         isSymbol = !isSymbol;
     123         updateKeys();
     124     }
     125
     126
     127     private void updateKeys() {
     128         for (List<Key> row : board) {
     129             for (Key key : row) {
     130                 key.update(capsDown, shiftDown, isSymbol);
     131             }
     132         }
     133     }
     134
     135     private final static boolean USE_SECONDARY_POPUP = false;
     136
     137     private static Region oldRoot;
     138     private static Timeline slideRootTimeline;
     139
     140     private static Popup vkPopup;
     141     private static Popup secondaryPopup;
     142     private static FXVK primaryVK;
     143
     144     private static Timeline slideInTimeline = new Timeline();
     145     private static Timeline slideOutTimeline = new Timeline();
     146
     147     private static FXVK secondaryVK;
     148     private static Timeline secondaryVKDelay;
     149     private static CharKey secondaryVKKey;
     150
     151     private Node attachedNode;
     152     private String vkType;
     153
     154     FXVK fxvk;
     155
     156     static final double VK_WIDTH = 640;
     157     static final double VK_HEIGHT = 243;
     158     static final double VK_PORTRAIT_HEIGHT = 326;
     159     static final double VK_SLIDE_MILLIS = 250;
     160     static final double PREF_KEY_WIDTH = 56;
     161     static final double PREF_PORTRAIT_KEY_WIDTH = 40;
     162     static final double PREF_KEY_HEIGHT = 56;
     163
     164     double keyWidth = PREF_KEY_WIDTH;
     165     double keyHeight = PREF_KEY_HEIGHT;
     166
     167     // Proxy for read-only Window.yProperty() so we can animate.
     168     private static DoubleProperty winY = new SimpleDoubleProperty();
     169     static {
     170         winY.addListener(new InvalidationListener() {
     171             @Override public void invalidated(Observable valueModel) {
     172                 if (vkPopup != null) {
     173                     vkPopup.setY(winY.get());
     174                 }
     175             }
     176         });
     177     }
     178
     179     private static void startSlideIn() {
     180         slideOutTimeline.stop();
     181         winY.set(vkPopup.getY());
     182         slideInTimeline.playFromStart();
     183     }
     184
     185     private static void startSlideOut() {
     186         slideInTimeline.stop();
     187         winY.set(vkPopup.getY());
     188         slideOutTimeline.playFromStart();
     189     }
     190
     191     EventHandler<InputEvent> unHideEventHandler;
     192
     193     private boolean isVKHidden = false;
     194
     195     private void registerUnhideHandler(final Node node) {
     196         if (unHideEventHandler == null) {
     197             unHideEventHandler = new EventHandler<InputEvent> () {
     198                 public void handle(InputEvent event) {
     199                     if (node != null && isVKHidden) {
     200                         double screenHeight = com.sun.javafx.Utils.getScreen(node).getBounds().getHeight();
     201                         if (fxvk.getHeight() > 0 && (vkPopup.getY() > screenHeight - fxvk.getHeight())) {
     202                             if (slideInTimeline.getStatus() != Animation.Status.RUNNING) {
     203                                 startSlideIn();
     204                             }
     205                         }
     206                     }
     207                     isVKHidden = false;
     208                 }
     209             };
     210         }
     211         attachedNode.addEventHandler(TOUCH_PRESSED, unHideEventHandler);
     212         attachedNode.addEventHandler(MOUSE_PRESSED, unHideEventHandler);
     213     }
     214
     215     private void unRegisterUnhideHandler(Node node) {
     216         if (unHideEventHandler != null) {
     217             node.removeEventHandler(TOUCH_PRESSED, unHideEventHandler);
     218             node.removeEventHandler(MOUSE_PRESSED, unHideEventHandler);
     219         }
     220     }
     221
     222     private void updateKeyboardType() {
     223         String oldType = vkType;
     224         int typeIndex = 0;
     225         Object typeValue = attachedNode.getProperties().get(FXVK.VK_TYPE_PROP_KEY);
     226         String typeStr = null;
     227         if (typeValue instanceof String) {
     228             typeStr = ((String)typeValue).toLowerCase(Locale.ROOT);
     229         }
     230         vkType = (typeStr != null ? typeStr : "text");
     231
     232         //VK type changed, rebuild
     233         if ( oldType == null || !vkType.equals(oldType) ) {
     234             rebuild();
     235         }
     236     }
     237
     238     public FXVKSkin(final FXVK fxvk) {
     239         super(fxvk, new BehaviorBase<>(fxvk, Collections.EMPTY_LIST));
     240         this.fxvk = fxvk;
     241
     242         StyleManager.getInstance().addUserAgentStylesheet("com/sun/javafx/scene/control/skin/caspian/fxvk.css");
     243
     244         fxvk.setFocusTraversable(false);
     245
     246         if (fxvk != secondaryVK) {
     247             //init secondary VK delay animation
     248             if (secondaryVKDelay == null) {
     249                 secondaryVKDelay = new Timeline();
     250             }
     251             KeyFrame kf = new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {
     252                 @Override public void handle(ActionEvent event) {
     253                     if (secondaryVKKey != null) {
     254                         showSecondaryVK(secondaryVKKey);
     255                     }
     256                 }
     257             });
     258             secondaryVKDelay.getKeyFrames().setAll(kf);
     259         }
     260
     261
     262         fxvk.attachedNodeProperty().addListener(new InvalidationListener() {
     263             @Override public void invalidated(Observable valueModel) {
     264                 Node oldNode = attachedNode;
     265                 attachedNode = fxvk.getAttachedNode();
     266                 isVKHidden = false;
     267                 if (fxvk != FXVK.vk) {
     268                     // This is not the current vk, so nothing more to do
     269                     return;
     270                 }
     271                 if (fxvk == secondaryVK) {
     272                     return;
     273                 }
     274
     275                 //close secondary VK if open
     276                 if (secondaryVK != null) {
     277                     secondaryVK.setAttachedNode(null);
     278                     secondaryPopup.hide();
     279                 }
     280
     281                 if (attachedNode != null) {
     282                     if (oldNode != null) {
     283                         unRegisterUnhideHandler(oldNode);
     284                     }
     285                     registerUnhideHandler(attachedNode);
     286                     updateKeyboardType();
     287
     288                     fxvk.setVisible(true);
     289
     290                     if (fxvk != secondaryVK) {
     291                         // init popup window and slide animations
     292                         if (vkPopup == null) {
     293                             vkPopup = new Popup();
     294                             vkPopup.setAutoFix(false);
     295
     296                             double screenHeight =
     297                                 com.sun.javafx.Utils.getScreen(attachedNode).getBounds().getHeight();
     298                             double screenVisualHeight =
     299                                 com.sun.javafx.Utils.getScreen(attachedNode).getVisualBounds().getHeight();
     300
     301                             screenVisualHeight = Math.min(screenHeight, screenVisualHeight + 4 /*??*/);
     302
     303                             slideInTimeline.getKeyFrames().setAll(
     304                                 new KeyFrame(Duration.millis(VK_SLIDE_MILLIS),
     305                                              new KeyValue(winY, screenHeight - VK_HEIGHT,
     306                                                           Interpolator.EASE_BOTH)));
     307                             slideOutTimeline.getKeyFrames().setAll(
     308                                 new KeyFrame(Duration.millis(VK_SLIDE_MILLIS),
     309                                              new KeyValue(winY, screenHeight, Interpolator.EASE_BOTH)));
     310                         }
     311
     312                         vkPopup.getContent().setAll(fxvk);
     313
     314                         //owner window has changed so hide VK and show with new owner
     315                         if (oldNode == null || oldNode.getScene() == null || oldNode.getScene().getWindow() != attachedNode.getScene().getWindow()) {
     316                             if (vkPopup.isShowing()) {
     317                                 vkPopup.hide();
     318                             }
     319                         }
     320
     321                         if (!vkPopup.isShowing()) {
     322                             Platform.runLater(new Runnable() {
     323                                 public void run() {
     324                                     Rectangle2D screenBounds =
     325                                         com.sun.javafx.Utils.getScreen(attachedNode).getBounds();
     326
     327                                     vkPopup.show(attachedNode.getScene().getWindow(),
     328                                                  (screenBounds.getWidth() - fxvk.prefWidth(-1)) / 2,
     329                                                  screenBounds.getHeight() - fxvk.prefHeight(-1));
     330                                 }
     331                             });
     332                         }
     333
     334                         if (oldNode == null || oldNode.getScene() != attachedNode.getScene()) {
     335                             double width = com.sun.javafx.Utils.getScreen(attachedNode).getBounds().getWidth();
     336                             fxvk.setPrefWidth(width);
     337                             fxvk.setMinWidth(USE_PREF_SIZE);
     338                             fxvk.setMaxWidth(USE_PREF_SIZE);
     339
     340                             fxvk.setPrefHeight(VK_HEIGHT);
     341                             fxvk.setMinHeight(USE_PREF_SIZE);
     342                         }
     343
     344                         if (fxvk.getHeight() > 0 &&
     345                                 (fxvk.getLayoutY() == 0 || fxvk.getLayoutY() > attachedNode.getScene().getHeight() - fxvk.getHeight())) {
     346                             startSlideIn();
     347                         }
     348                     }
     349                 } else {
     350                     if (fxvk != secondaryVK) {
     351                         if (oldNode != null) {
     352                             unRegisterUnhideHandler(oldNode);
     353                         }
     354                         startSlideOut();
     355                     }
     356
     357                     if (secondaryVK != null) {
     358                         secondaryVK.setAttachedNode(null);
     359                         secondaryPopup.hide();
     360                     }
     361                     return;
     362                 }
     363             }
     364         });
     365     }
     366
     367     /**
     368      * Replaces all children of this VirtualKeyboardSkin based on the keyboard
     369      * type set on the VirtualKeyboard.
     370      */
     371     private void rebuild() {
     372         if (fxvk == secondaryVK) {
     373             //build secondary VK
     374             if (secondaryVK.chars == null) {
     375             } else {
     376                 int nKeys = secondaryVK.chars.length;
     377                 int nRows = (int)Math.floor(Math.sqrt(Math.max(1, nKeys - 2)));
     378                 int nKeysPerRow = (int)Math.ceil(nKeys / (double)nRows);
     379
     380                 Key tmpKey;
     381                 List<List<Key>> rows = new ArrayList<List<Key>>(2);
     382
     383                 for (int i = 0; i < nRows; i++) {
     384                     int start = i * nKeysPerRow;
     385                     int end = Math.min(start + nKeysPerRow, nKeys);
     386                     if (start >= end)
     387                         break;
     388
     389                     List<Key> keys = new ArrayList<Key>(nKeysPerRow);
     390                     for (int j = start; j < end; j++) {
     391                         tmpKey = new CharKey(secondaryVK.chars[j], null, null);
     392                         tmpKey.col= (j - start) * 2;
     393                         tmpKey.colSpan = 2;
     394                         for (String sc : tmpKey.getStyleClass()) {
     395                             tmpKey.text.getStyleClass().add(sc + "-text");
     396                             tmpKey.altText.getStyleClass().add(sc + "-alttext");
     397                             tmpKey.icon.getStyleClass().add(sc + "-icon");
     398                         }
     399                         if (secondaryVK.chars[j] != null && secondaryVK.chars[j].length() > 1) {
     400                             tmpKey.text.getStyleClass().add("multi-char-text");
     401                         }
     402                         keys.add(tmpKey);
     403                     }
     404                     rows.add(keys);
     405                 }
     406                 board = rows;
     407
     408                 getChildren().clear();
     409                 numCols = 0;
     410                 for (List<Key> row : board) {
     411                     for (Key key : row) {
     412                         numCols = Math.max(numCols, key.col + key.colSpan);
     413                     }
     414                     getChildren().addAll(row);
     415                 }
     416             }
     417         } else {
     418             String boardName;
     419
     420             switch (vkType) {
     421                 case "text":
     422                     boardName = "TextBoard";
     423                     break;
     424                 case "numeric":
     425                     boardName = "NumericBoard";
     426                     break;
     427                 case "url":
     428                     boardName = "UrlBoard";
     429                     break;
     430                 case "email":
     431                     boardName = "EmailBoard";
     432                     break;
     433                 default:
     434                     boardName = "TextBoard";
     435             }
     436
     437             board = loadBoard(boardName);
     438             getChildren().clear();
     439             numCols = 0;
     440             for (List<Key> row : board) {
     441                 for (Key key : row) {
     442                     numCols = Math.max(numCols, key.col + key.colSpan);
     443                 }
     444                 getChildren().addAll(row);
     445             }
     446         }
     447
     448     }
     449
     450     // This skin is designed such that it gives equal widths to all columns. So
     451     // the pref width is just some hard-coded value (although I could have maybe
     452     // done it based on the pref width of a text node with the right font).
     453     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
     454         return leftInset + (56 * numCols) + rightInset;
     455     }
     456
     457     // Pref height is just some value. This isn't overly important.
     458     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
     459         return topInset + (80 * 5) + bottomInset;
     460     }
     461
     462     // Lays the buttons comprising the current keyboard out.
     463     @Override
     464     protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
     465         // I have fixed width columns, all the same.
     466         int numRows = board.size();
     467         final double colWidth = ((contentWidth - ((numCols - 1) * GAP)) / numCols);
     468         double rowHeight = ((contentHeight - ((numRows - 1) * GAP)) / numRows);
     469         double rowY = contentY;
     470         for (List<Key> row : board) {
     471             for (Key key : row) {
     472                 double startX = contentX + (key.col * (colWidth + GAP));
     473                 double width = (key.colSpan * (colWidth + GAP)) - GAP;
     474                 key.resizeRelocate((int)(startX + .5), (int)(rowY + .5),
     475                                    width, rowHeight);
     476             }
     477             rowY += rowHeight + GAP;
     478         }
     479     }
     480
     481
     482     /**
     483      * A Key on the virtual keyboard. This is simply a Region. Some information
     484      * about the key relative to other keys on the layout is given by the col
     485      * and colSpan fields.
     486      */
     487     private class Key extends Region {
     488         int col = 0;
     489         int colSpan = 1;
     490         protected final Text text;
     491         protected final Text altText;
     492         protected final Region icon;
     493
     494         protected Key() {
     495             icon = new Region();
     496             text = new Text();
     497             text.setTextOrigin(VPos.TOP);
     498             altText = new Text();
     499             altText.setTextOrigin(VPos.TOP);
     500             getChildren().setAll(text, altText, icon);
     501             getStyleClass().setAll("key");
     502             addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() {
     503                 @Override public void handle(MouseEvent event) {
     504                     if (event.getEventType() == MouseEvent.MOUSE_PRESSED)
     505                         press();
     506                     else if (event.getEventType() == MouseEvent.MOUSE_RELEASED)
     507                         release();
     508                 }
     509             });
     510         }
     511         protected void press() { }
     512         protected void release() {
     513             clearShift();
     514         }
     515
     516         public void update(boolean capsDown, boolean shiftDown, boolean isSymbol) { }
     517
     518         @Override protected void layoutChildren() {
     519             final double left = snappedLeftInset();
     520             final double top = snappedTopInset();
     521             final double width = getWidth() - left - snappedRightInset();
     522             final double height = getHeight() - top - snappedBottomInset();
     523
     524             text.setVisible(icon.getBackground() == null);
     525             double contentPrefWidth = text.prefWidth(-1);
     526             double contentPrefHeight = text.prefHeight(-1);
     527             text.resizeRelocate(
     528                     (int) (left + ((width - contentPrefWidth) / 2) + .5),
     529                     (int) (top + ((height - contentPrefHeight) / 2) + .5),
     530                     (int) contentPrefWidth,
     531                     (int) contentPrefHeight);
     532
     533             altText.setVisible(icon.getBackground() == null && altText.getText().length() > 0);
     534             contentPrefWidth = altText.prefWidth(-1);
     535             contentPrefHeight = altText.prefHeight(-1);
     536             altText.resizeRelocate(
     537                     (int) (left + ((width - contentPrefWidth) / 2) + .5 + width/2),
     538                     (int) (top + ((height - contentPrefHeight) / 2) + .5 - height/2),
     539                     (int) contentPrefWidth,
     540                     (int) contentPrefHeight);
     541
     542             icon.resizeRelocate(left-8, top-8, width+16, height+16);
     543         }
     544
     545     }
     546
     547     /**
     548      * Any key on the keyboard which will send a KeyEvent to the client. This
     549      * class just maintains the state and logic for firing an event, using the
     550      * "chars" and "code" as the values sent in the event. A subclass must set
     551      * these appropriately.
     552      */
     553     private class TextInputKey extends Key {
     554         String chars = "";
     555
     556         protected void press() {
     557         }
     558         protected void release() {
     559             if (fxvk != secondaryVK && secondaryPopup != null && secondaryPopup.isShowing()) {
     560                 return;
     561             }
     562             sendKeyEvents();
     563             if (fxvk == secondaryVK) {
     564                 showSecondaryVK(null);
     565             }
     566             super.release();
     567         }
     568
     569         protected void sendKeyEvents() {
     570             Node target = fxvk.getAttachedNode();
     571             if (target instanceof EventTarget) {
     572                 if (chars != null) {
     573                     target.fireEvent(new KeyEvent(KeyEvent.KEY_TYPED, chars, "", KeyCode.UNDEFINED, shiftDown, false, false, false));
     574                 }
     575             }
     576         }
     577     }
     578
     579     /**
     580      * A key which has a letter, a number or symbol on it
     581      *
     582      */
     583     private class CharKey extends TextInputKey {
     584         private final String letterChars;
     585         private final String altChars;
     586         private final String[] moreChars;
     587
     588         private CharKey(String letter, String alt, String[] moreChars) {
     589             setId(letter);
     590             this.letterChars = letter;
     591             this.altChars = alt;
     592             this.moreChars = moreChars;
     593             this.chars = this.letterChars;
     594
     595             text.setText(this.chars);
     596             altText.setText(this.altChars);
     597
     598             handleSecondaryVK(letter, alt, moreChars);
     599         }
     600
     601
     602         private void handleSecondaryVK(String letter, String alt, String[] moreChars) {
     603             // If key has only one char (alternative char is the same, and it has no more chars),
     604             // secondaryVK will not pop-up
     605             if (letter.equals(alt) && moreChars == null) {
     606                 return;
     607             } else {
     608                 if (fxvk != secondaryVK) {
     609                     setOnMousePressed(new EventHandler<MouseEvent>() {
     610                         @Override public void handle(MouseEvent event) {
     611                             showSecondaryVK(null);
     612                             secondaryVKKey = CharKey.this;
     613                             secondaryVKDelay.playFromStart();
     614                         }
     615                     });
     616
     617                     setOnMouseReleased(new EventHandler<MouseEvent>() {
     618                         @Override public void handle(MouseEvent event) {
     619                             secondaryVKDelay.stop();
     620                         }
     621                     });
     622                 }
     623             }
     624         }
     625
     626         @Override public void update(boolean capsDown, boolean shiftDown, boolean isSymbol) {
     627             if (isSymbol) {
     628                 chars = altChars;
     629                 text.setText(chars);
     630                 if (moreChars != null && moreChars.length > 0 && !Character.isLetter(moreChars[0].charAt(0))) {
     631                     altText.setText(moreChars[0]);
     632                 } else {
     633                     altText.setText(null);
     634                 }
     635             } else {
     636                 chars = (capsDown || shiftDown) ? letterChars.toUpperCase() : letterChars.toLowerCase();
     637                 text.setText(chars);
     638                 altText.setText(altChars);
     639             }
     640         }
     641     }
     642
     643     /**
     644      * One of several TextInputKeys which have super powers, such as "Tab" and
     645      * "Return" and "Backspace". These keys still send events to the client,
     646      * but may also have additional state related functionality on the keyboard
     647      * such as the "Shift" key.
     648      */
     649     private class SuperKey extends TextInputKey {
     650         private SuperKey(String letter, String code) {
     651             this.chars = code;
     652             text.setText(letter);
     653             getStyleClass().add("special");
     654             setId(letter);
     655         }
     656     }
     657
     658     /**
     659      * Some keys actually do need to use KeyCode for pressed / released events,
     660      * and BackSpace is one of them.
     661      */
     662     private class KeyCodeKey extends SuperKey {
     663         private KeyCode code;
     664
     665         private KeyCodeKey(String letter, String c, KeyCode code) {
     666             super(letter, c);
     667             this.code = code;
     668             setId(letter);
     669         }
     670
     671         protected void sendKeyEvents() {
     672             Node target = fxvk.getAttachedNode();
     673             if (target instanceof EventTarget) {
     674                 target.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED, chars, code, shiftDown, false, false, false));
     675                 target.fireEvent(new KeyEvent(KeyEvent.KEY_TYPED, chars, "", KeyCode.UNDEFINED, shiftDown, false, false, false));
     676                 target.fireEvent(new KeyEvent(KeyEvent.KEY_RELEASED, KeyEvent.CHAR_UNDEFINED, chars, code, shiftDown, false, false, false));
     677             }
     678         }
     679     }
     680
     681     /**
     682      * These keys only manipulate the state of the keyboard and never
     683      * send key events to the client. For example, "Hide", "Caps Lock",
     684      * etc are all KeyboardStateKeys.
     685      */
     686     private class KeyboardStateKey extends Key {
     687         private final String defaultText;
     688         private final String toggledText;
     689
     690         private KeyboardStateKey(String defaultText, String toggledText) {
     691             this.defaultText = defaultText;
     692             this.toggledText = toggledText;
     693             text.setText(this.defaultText);
     694             setId(this.defaultText);
     695             getStyleClass().add("special");
     696         }
     697
     698         @Override public void update(boolean capsDown, boolean shiftDown, boolean isSymbol) {
     699             //change icon
     700
     701             if (isSymbol) {
     702                 text.setText(this.toggledText);
     703                 setId(this.toggledText);
     704             } else {
     705                 text.setText(this.defaultText);
     706                 setId(this.defaultText);
     707             }
     708         }
     709     }
     710
     711     private void showSecondaryVK(final CharKey key) {
     712         if (key != null) {
     713             primaryVK = fxvk;
     714             final Node textInput = primaryVK.getAttachedNode();
     715
     716             if (secondaryVK == null) {
     717                 secondaryVK = new FXVK();
     718                 //secondaryVK.getStyleClass().addAll("fxvk-secondary", "fxvk-portrait");
     719                 secondaryVK.setSkin(new FXVKSkin(secondaryVK));
     720                 secondaryVK.getStyleClass().setAll("fxvk-secondary");
     721                 secondaryPopup = new Popup();
     722                 secondaryPopup.setAutoHide(true);
     723                 secondaryPopup.getContent().add(secondaryVK);
     724             }
     725
     726             secondaryVK.chars=null;
     727             ArrayList<String> secondaryList = new ArrayList<String>();
     728
     729             // Add primary character
     730             if (!isSymbol) {
     731                 if (key.letterChars != null && key.letterChars.length() > 0) {
     732                     if (shiftDown || capsDown) {
     733                         secondaryList.add(key.letterChars.toUpperCase());
     734                     } else {
     735                         secondaryList.add(key.letterChars);
     736                     }
     737                 }
     738             }
     739
     740             // Add secondary character
     741             if (key.altChars != null && key.altChars.length() > 0) {
     742                 if (shiftDown || capsDown) {
     743                     secondaryList.add(key.altChars.toUpperCase());
     744                 } else {
     745                     secondaryList.add(key.altChars);
     746                 }
     747             }
     748
     749             // Add more letters
     750             if (key.moreChars != null && key.moreChars.length > 0) {
     751                 if (isSymbol) {
     752                     //Add non-letters
     753                     for (String ch : key.moreChars) {
     754                         if (!Character.isLetter(ch.charAt(0))) {
     755                             secondaryList.add(ch);
     756                         }
     757                     }
     758                  } else {
     759                     //Add letters
     760                     for (String ch : key.moreChars) {
     761                         if (Character.isLetter(ch.charAt(0))) {
     762                             if (shiftDown || capsDown) {
     763                                 secondaryList.add(ch.toUpperCase());
     764                             } else {
     765                                 secondaryList.add(ch);
     766                             }
     767                         }
     768                     }
     769                 }
     770             }
     771             secondaryVK.chars = secondaryList.toArray(new String[secondaryList.size()]);
     772
     773             if (secondaryVK.chars.length > 1) {
     774                 if (secondaryVK.getSkin() != null) {
     775                     ((FXVKSkin)secondaryVK.getSkin()).rebuild();
     776                 }
     777
     778                 secondaryVK.setAttachedNode(textInput);
     779                 FXVKSkin primarySkin = (FXVKSkin)primaryVK.getSkin();
     780                 FXVKSkin secondarySkin = (FXVKSkin)secondaryVK.getSkin();
     781                 //Insets insets = secondarySkin.getInsets();
     782                 int nKeys = secondaryVK.chars.length;
     783                 int nRows = (int)Math.floor(Math.sqrt(Math.max(1, nKeys - 2)));
     784                 int nKeysPerRow = (int)Math.ceil(nKeys / (double)nRows);
     785
     786                 final double w = snappedLeftInset() + snappedRightInset() +
     787                                  nKeysPerRow * PREF_PORTRAIT_KEY_WIDTH + (nKeysPerRow - 1) * GAP;
     788                 final double h = snappedTopInset() + snappedBottomInset() +
     789                                  nRows * PREF_KEY_HEIGHT + (nRows-1) * GAP;
     790
     791                 secondaryVK.setPrefWidth(w);
     792                 secondaryVK.setMinWidth(USE_PREF_SIZE);
     793                 secondaryVK.setPrefHeight(h);
     794                 secondaryVK.setMinHeight(USE_PREF_SIZE);
     795                 Platform.runLater(new Runnable() {
     796                     public void run() {
     797                         // Position popup on screen
     798                         Point2D nodePoint =
     799                             com.sun.javafx.Utils.pointRelativeTo(key, w, h, HPos.CENTER, VPos.TOP,
     800                                                                  5, -3, true);
     801                         double x = nodePoint.getX();
     802                         double y = nodePoint.getY();
     803                         Scene scene = key.getScene();
     804                         x = Math.min(x, scene.getWindow().getX() + scene.getWidth() - w);
     805                         secondaryPopup.show(key.getScene().getWindow(), x, y);
     806                     }
     807                 });
     808             }
     809         } else {
     810             if (secondaryVK != null) {
     811                 secondaryVK.setAttachedNode(null);
     812                 secondaryPopup.hide();
     813             }
     814         }
     815     }
     816
     817
     818
     819     private List<List<Key>> loadBoard(String boardName) {
     820         try {
     821             List<List<Key>> rows = new ArrayList<List<Key>>(5);
     822             List<Key> keys = new ArrayList<Key>(20);
     823
     824             InputStream boardFile = FXVKSkin.class.getResourceAsStream(boardName + ".txt");
     825             BufferedReader reader = new BufferedReader(new InputStreamReader(boardFile));
     826             String line;
     827             // A pointer to the current column. This will be incremented for every string
     828             // of text, or space.
     829             int c = 0;
     830             // The col at which the key will be placed
     831             int col = 0;
     832             // The number of columns that the key will span
     833             int colSpan = 1;
     834             // Whether the "chars" is an identifier, like $shift or $SymbolBoard, etc.
     835             boolean identifier = false;
     836             // The textual content of the Key
     837             List<String> charsList = new ArrayList<String>(10);
     838
     839             while ((line = reader.readLine()) != null) {
     840                 if (line.length() == 0 || line.charAt(0) == '#') {
     841                     continue;
     842                 }
     843                 // A single line represents a single row of buttons
     844                 for (int i=0; i<line.length(); i++) {
     845                     char ch = line.charAt(i);
     846
     847                     // Process the char
     848                     if (ch == ' ') {
     849                         c++;
     850                     } else if (ch == '[') {
     851                         // Start of a key
     852                         col = c;
     853                         charsList = new ArrayList<String>(10);
     854                         identifier = false;
     855                     } else if (ch == ']') {
     856                         String chars = "";
     857                         String alt = null;
     858                         String[] moreChars = null;
     859
     860                         for (int idx = 0; idx < charsList.size(); idx++) {
     861                             charsList.set(idx, FXVKCharEntities.get(charsList.get(idx)));
     862                         }
     863
     864                         int listSize = charsList.size();
     865                         if (listSize > 0) {
     866                             chars = charsList.get(0);
     867                             if (listSize > 1) {
     868                                 alt = charsList.get(1);
     869                                 if (listSize > 2) {
     870                                     moreChars = charsList.subList(2, listSize).toArray(new String[listSize - 2]);
     871                                 }
     872                             }
     873                         }
     874
     875                         // End of a key
     876                         colSpan = c - col;
     877                         Key key;
     878                         if (identifier) {
     879                             if ("$shift".equals(chars)) {
     880                                 key = new KeyboardStateKey("", null) {
     881                                     @Override protected void release() {
     882                                         pressShift();
     883                                     }
     884
     885                                     @Override public void update(boolean capsDown, boolean shiftDown, boolean isSymbol) {
     886                                         if (isSymbol) {
     887                                             this.setDisable(true);
     888                                             this.setVisible(false);
     889                                         } else {
     890                                             if (capsDown) {
     891                                                 icon.getStyleClass().remove("shift-icon");
     892                                                 icon.getStyleClass().add("capslock-icon");
     893                                             } else {
     894                                                 icon.getStyleClass().remove("capslock-icon");
     895                                                 icon.getStyleClass().add("shift-icon");
     896                                             }
     897                                             this.setDisable(false);
     898                                             this.setVisible(true);
     899                                         }
     900                                     }
     901                                 };
     902                                 key.getStyleClass().add("shift");
     903
     904                             } else if ("$SymbolABC".equals(chars)) {
     905                                 key = new KeyboardStateKey("!#123", "ABC") {
     906                                     @Override protected void release() {
     907                                         pressSymbolABC();
     908                                     }
     909                                 };
     910                             } else if ("$backspace".equals(chars)) {
     911                                 key = new KeyCodeKey("backspace", "\b", KeyCode.BACK_SPACE);
     912                                 key.getStyleClass().add("backspace");
     913
     914                             } else if ("$enter".equals(chars)) {
     915                                 key = new KeyCodeKey("enter", "\n", KeyCode.ENTER);
     916                                 key.getStyleClass().add("enter");
     917                             } else if ("$tab".equals(chars)) {
     918                                 key = new KeyCodeKey("tab", "\t", KeyCode.TAB);
     919                             } else if ("$space".equals(chars)) {
     920                                 key = new CharKey(" ", " ", null);
     921                             } else if ("$clear".equals(chars)) {
     922                                 key = new SuperKey("clear", "");
     923                             } else if ("$.org".equals(chars)) {
     924                                 key = new SuperKey(".org", ".org");
     925                             } else if ("$.com".equals(chars)) {
     926                                 key = new SuperKey(".com", ".com");
     927                             } else if ("$.net".equals(chars)) {
     928                                 key = new SuperKey(".net", ".net");
     929                             } else if ("$oracle.com".equals(chars)) {
     930                                 key = new SuperKey("oracle.com", "oracle.com");
     931                             } else if ("$gmail.com".equals(chars)) {
     932                                 key = new SuperKey("gmail.com", "gmail.com");
     933                             } else if ("$hide".equals(chars)) {
     934                                 key = new KeyboardStateKey("Hide", null) {
     935                                     @Override protected void release() {
     936                                         isVKHidden = true;
     937                                         startSlideOut();
     938                                     }
     939                                 };
     940                                 key.getStyleClass().add("hide");
     941                             } else if ("$undo".equals(chars)) {
     942                                 key = new SuperKey("undo", "");
     943                             } else if ("$redo".equals(chars)) {
     944                                 key = new SuperKey("redo", "");
     945                             } else {
     946                                 //Unknown Key
     947                                 key = null;
     948                             }
     949                         } else {
     950                             key = new CharKey(chars, alt, moreChars);
     951                         }
     952                         if (key != null) {
     953                             key.col = col;
     954                             key.colSpan = colSpan;
     955                             for (String sc : key.getStyleClass()) {
     956                                 key.text.getStyleClass().add(sc + "-text");
     957                                 key.altText.getStyleClass().add(sc + "-alttext");
     958                                 key.icon.getStyleClass().add(sc + "-icon");
     959                             }
     960                             if (chars != null && chars.length() > 1) {
     961                                 key.text.getStyleClass().add("multi-char-text");
     962                             }
     963                             if (alt != null && alt.length() > 1) {
     964                                 key.altText.getStyleClass().add("multi-char-text");
     965                             }
     966
     967                             keys.add(key);
     968                         }
     969                     } else {
     970                         // Normal textual characters. Read all the way up to the
     971                         // next ] or space
     972                         for (int j=i; j<line.length(); j++) {
     973                             char c2 = line.charAt(j);
     974                             boolean e = false;
     975                             if (c2 == '\\') {
     976                                 j++;
     977                                 i++;
     978                                 e = true;
     979                                 c2 = line.charAt(j);
     980                             }
     981
     982                             if (c2 == '$' && !e) {
     983                                 identifier = true;
     984                             }
     985
     986                             if (c2 == '|' && !e) {
     987                                 charsList.add(line.substring(i, j));
     988                                 i = j + 1;
     989                             } else if ((c2 == ']' || c2 == ' ') && !e) {
     990                                 charsList.add(line.substring(i, j));
     991                                 i = j-1;
     992                                 break;
     993                             }
     994                         }
     995                         c++;
     996                     }
     997                 }
     998
     999                 c = 0;
    1000                 col = 0;
    1001                 rows.add(keys);
    1002                 keys = new ArrayList<Key>(20);
    1003             }
    1004             reader.close();
    1005             return rows;
    1006         } catch (Exception e) {
    1007             e.printStackTrace();
    1008             return Collections.emptyList();
    1009         }
    1010     }
    1011 }
    --- EOF ---