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