Search code examples
javaswingjtextfield

JTextField Display and Actual Text - Persian/Arabic Numbers


We are looking for a way to keep two types of texts for a JTextField

  1. Actual value is the value which the user input using keyboard
  2. Converted Value is a value which the user see inside that text field (The reason of doing this is that we want to display to our users Persian digits while he/she is typing - Only Display)

And finally the actual text will save inside our database NOT the Display text

We have created the text converter for the Display BUT we don't know how to apply this Scenario to the JTextField


Solution

  • I went through this and I came up with this solution.

    First we need to show the user persian/arabic digits when they have chosen Persian/Arabic keyboard in their OS and they're typing numbers inside their JTextField. To achieve this I wrote this custom DocumentFilter :

    public class PersianNumberDocumentFilter extends DocumentFilter {
        private static final String REPLACE_CHARS = "0123456789.";
    
        @Override
        public void insertString(FilterBypass fb, int offset, String text,
                                 AttributeSet attr) throws BadLocationException {
            if (text != null && !text.isEmpty() && REPLACE_CHARS.contains(text)) {
                text = doSwap(text);
            }
            super.insertString(fb, offset, text, attr);
        }
    
        @Override
        public void replace(FilterBypass fb, int offset, int length, String text,
                            AttributeSet attrs) throws BadLocationException {
            if (text != null && !text.isEmpty() && REPLACE_CHARS.contains(text)) {
                text = doSwap(text);
            }
            super.replace(fb, offset, length, text, attrs);
        }
    
        @Override
        public void remove(FilterBypass fb, int offset, int length)
                throws BadLocationException {
            super.remove(fb, offset, length);
        }
    
        public String doSwap(String text) {
            InputContext context = InputContext.getInstance();
            String windowsKeyboardSelected = context.getLocale().toString();
            if (!windowsKeyboardSelected.contains("fa") && !windowsKeyboardSelected.contains("ar")) {
                return text;
            }
    
            StringBuilder sb = new StringBuilder();
            for (char c : text.toCharArray()) {
                if (REPLACE_CHARS.contains(String.valueOf(c))) {
                    if (c == '.') {
                        c = ',';
                    } else {
                        c = (char) ('\u06F0' - '0' + c);
                        //c = (char) ('\u0660' - '0' + c); Arabic Number Digits
                    }
                }
                sb.append(c);
            }
    
            return sb.toString();
        }
    }
    

    Next we need a utility class for number and text conversion from and to Persian/Arabic. I wrote this utility class for this matter:

    public class PersianNumber {
        private static HashMap<Character, String> unicodeMap = new HashMap<>();
        private static HashMap<Character, String> unicodeMapArabic = new HashMap<>();
        private static HashMap<Character, String> unicodeMapViceVera = new HashMap<>();
    
        static {
            //English/Persian Numbers Map
            unicodeMap.put('0', "\u06F0");
            unicodeMap.put('1', "\u06F1");
            unicodeMap.put('2', "\u06F2");
            unicodeMap.put('3', "\u06F3");
            unicodeMap.put('4', "\u06F4");
            unicodeMap.put('5', "\u06F5");
            unicodeMap.put('6', "\u06F6");
            unicodeMap.put('7', "\u06F7");
            unicodeMap.put('8', "\u06F8");
            unicodeMap.put('9', "\u06F9");
    
            //English/Arabic Numbers Map
            unicodeMapArabic.put('0',"\u0660");
            unicodeMapArabic.put('1',"\u0661");
            unicodeMapArabic.put('2',"\u0662");
            unicodeMapArabic.put('3',"\u0663");
            unicodeMapArabic.put('4',"\u0664");
            unicodeMapArabic.put('5',"\u0665");
            unicodeMapArabic.put('6',"\u0666");
            unicodeMapArabic.put('7',"\u0667");
            unicodeMapArabic.put('8',"\u0668");
            unicodeMapArabic.put('9',"\u0669");
    
            //Persian English Numbers Map
            unicodeMapViceVera.put('\u06F0', "0");
            unicodeMapViceVera.put('\u06F1', "1");
            unicodeMapViceVera.put('\u06F2', "2");
            unicodeMapViceVera.put('\u06F3', "3");
            unicodeMapViceVera.put('\u06F4', "4");
            unicodeMapViceVera.put('\u06F5', "5");
            unicodeMapViceVera.put('\u06F6', "6");
            unicodeMapViceVera.put('\u06F7', "7");
            unicodeMapViceVera.put('\u06F8', "8");
            unicodeMapViceVera.put('\u06F9', "9");
            //Arabic English Numbers Map
            unicodeMapViceVera.put('\u0660', "0");
            unicodeMapViceVera.put('\u0661', "1");
            unicodeMapViceVera.put('\u0662', "2");
            unicodeMapViceVera.put('\u0663', "3");
            unicodeMapViceVera.put('\u0664', "4");
            unicodeMapViceVera.put('\u0665', "5");
            unicodeMapViceVera.put('\u0666', "6");
            unicodeMapViceVera.put('\u0667', "7");
            unicodeMapViceVera.put('\u0668', "8");
            unicodeMapViceVera.put('\u0669', "9");
        }
    
        public static String getPersianNumber(String englishNumber) {
            if (!englishNumber.matches("-?\\d+(\\.\\d+)?")) {
                throw new NumberFormatException("the input [" + englishNumber + "] is not a number!");
            }
    
            char[] numChars = englishNumber.toCharArray();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < numChars.length; i++) {
                builder.append(unicodeMap.get(numChars[i]));
            }
            return builder.toString();
        }
    
        public static String getEnglishNumber(String persianNumber) {
            char[] numChars = persianNumber.toCharArray();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < numChars.length; i++) {
                String value = unicodeMapViceVera.get(numChars[i]);
                if(value == null){
                    builder.setLength(0);
                    builder.append(persianNumber);
                    break;
                }
                builder.append(unicodeMapViceVera.get(numChars[i]));
            }
            return builder.toString();
        }
    
        public static boolean isAPersianNumber(String persianNumber){
            char[] numChars = persianNumber.toCharArray();
            for (int i = 0; i < numChars.length; i++) {
                String value = unicodeMapViceVera.get(numChars[i]);
                if(value == null){
                    return false;
                }
            }
            return true;
        }
    
        public static String convertToStringWithPersianNumber(String input){
            StringBuilder sb = new StringBuilder();
    
            for(char c : input.toCharArray()){
                String ch = unicodeMap.get(c);
                if(ch != null){
                    sb.append(ch);
                    continue;
                }
                sb.append(c);
            }
    
            return sb.toString();
        }
    
        public static String convertToStringWithEnglishNumber(String input){
            StringBuilder sb = new StringBuilder();
    
            for(char c : input.toCharArray()){
                String ch = unicodeMapViceVera.get(c);
                if(ch != null){
                    sb.append(ch);
                    continue;
                }
                sb.append(c);
            }
    
            return sb.toString();
        }
    
        public static void main(String[] args) {
            System.out.println(getEnglishNumber("۹۶۵۹۴۵۴"));
            System.out.println(getPersianNumber("9999999999999651221"));
            System.out.println(getPersianNumber(59683399623213L + ""));
        }
    }
    

    Finally We need a Custom JTextField because we want control our inputs and outputs and convert them accordingly from-to our needs:

    public class JPersianTextField2 extends JTextField {
    
    
        public JPersianTextField2() {
    //        Hopefully someday (Oracle Love Iranians) and create This Locale for us
            Locale locale = new Locale("fa", "IR");
    //        Locale locale = new Locale("ar", "KW");
            ComponentOrientation farsiOrientation = ComponentOrientation.getOrientation(locale);
            super.applyComponentOrientation(farsiOrientation);
            PlainDocument doc = (PlainDocument) super.getDocument();
            doc.setDocumentFilter(new PersianNumberDocumentFilter());
            super.setColumns(25);
        }
    
        @Override
        public String getText() {
            String tmp = super.getText();
            if (tmp != null && !tmp.isEmpty())
                return PersianNumber2.convertToStringWithEnglishNumber(tmp);
            else
                return super.getText();
        }
    
        @Override
        public void setText(String t) {
            if (t != null && !t.isEmpty())
                super.setText(PersianNumber2.convertToStringWithPersianNumber(t));
        }
    
    }
    

    With this custom JTextField in place you can convert to English digit when you want to fetch the result of the JTextField and convert to Persian digit when you want to set a String which has English digits.

    And Finally here is the test for the component :

    public class MyFormatterTest extends JFrame {
    
        public MyFormatterTest() {
    
            setTitle("Example");
            JPanel panel = new JPanel();
            JLabel label = new JLabel("text :");
            JPersianTextField2 tf = new JPersianTextField2();
            tf.setText("سلام من مهدی هستم برنامه نویس ارشد 31 ساله"); //Hi This is mehdi, 31 years old Sr. software developer
            panel.add(label);
            panel.add(tf);
    
            JButton button2 = new JButton();
            button2.setText("getText");
            button2.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(tf.getText());
                }
            });
    
            panel.add(button2);
    
    
            getContentPane().add(panel, BorderLayout.SOUTH);
            pack();
    
        }
    
        public static void main(String[] args) {
    
            //Available Locales inside Your JVM
            for (Locale l : Locale.getAvailableLocales()) {
                System.out.println(l.getCountry() + ":" + l.getLanguage());
            }
    
            MyFormatterTest mfe = new MyFormatterTest();
            mfe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            mfe.setVisible(true);
        }
    }
    

    Hope this is useful for you ...