Search code examples
blackberryeclipse-pluginblackberry-basiceditfield

How to format number using fieldchangelistener


I want to format a number as the user enters the number in an edit field.

I am using below code to format the number as the user changes focus to another control, using the onfocus() function:

    public static String formatNumber(double number, int decimals, String digitGrouping){
            Formatter f = new Formatter("en");
            String rawNumber = f.formatNumber(number, decimals+1);

            String rawIntString = rawNumber.substring(0, rawNumber.indexOf(".")); //Basically intString without digit grouping
            StringBuffer intString = new StringBuffer();
            StringBuffer decString = new StringBuffer(rawNumber.substring(rawNumber.indexOf(".")+1));
            StringBuffer formattedNumber = new StringBuffer();
            int workingVal = 0;
            int newNum = 0;
            boolean roundNext;

            //Add digit grouping
            int grouplen = 0;
            int firstDigit;
            if(rawIntString.charAt(0) == '-'){
                firstDigit = 1;
            }else{
                firstDigit = 0;
            }
            for(int n=rawIntString.length()-1;n>=firstDigit;n--){
                intString.insert(0, rawIntString.substring(n, n+1));
                grouplen++;
                if(grouplen == 3 && n>firstDigit){
                    intString.insert(0, digitGrouping);
                    grouplen = 0;
                }
            }

            //First, check the last digit
            workingVal = Integer.parseInt(String.valueOf(decString.charAt(decString.length()-1)));
            if(workingVal>=5){
                roundNext = true;
            }else{
                roundNext = false;
            }
            //Get the decimal values, round if needed, and add to formatted string buffer
            for(int n=decString.length()-2;n>=0;n--){
                workingVal = Integer.parseInt(String.valueOf(decString.charAt(n)));
                if(roundNext == true){
                    newNum = workingVal + 1;
                    if(newNum == 10){
                        roundNext = true;
                        newNum = 0;
                    }else{
                        roundNext = false;
                    }
                    formattedNumber.insert(0, newNum);
                }else{
                    formattedNumber.insert(0, workingVal);
                }
            }
            //Now get the integer values, round if needed, and add to formatted string buffer
            formattedNumber.insert(0, ".");
            for(int n=intString.length()-1;n>=0;n--){
                try{
                    workingVal = Integer.parseInt(String.valueOf(intString.charAt(n)));
                }catch(Exception e){
                    formattedNumber.insert(0, intString.charAt(n));
                    continue;
                }
                if(roundNext == true){
                    newNum = workingVal + 1;
                    if(newNum == 10){
                        roundNext = true;
                        newNum = 0;
                    }else{
                        roundNext = false;
                    }
                    formattedNumber.insert(0, newNum);
                }else{
                    formattedNumber.insert(0, workingVal);
                }   
            }

            //Just in case its a number like 9999.99999 (if it rounds right to the end
            if(roundNext == true){
                formattedNumber.insert(0, 1);

            }   

            //re-add the minus sign if needed
            if(firstDigit == 1) formattedNumber.insert(0, rawIntString.charAt(0));

            if(digitGrouping.length() > 0){
                if(formattedNumber.toString().indexOf(".") == -1){
                    //no decimal
                    if(formattedNumber.toString().indexOf(digitGrouping) > 3+firstDigit){
                        formattedNumber.insert(1+firstDigit, digitGrouping);
                    }

                    if(formattedNumber.toString().length() == 4+firstDigit){
                        formattedNumber.insert(1+firstDigit, digitGrouping);
                    }
                }else{
                    //no decimal
                    if(formattedNumber.toString().indexOf(digitGrouping) > 3+firstDigit){
                        formattedNumber.insert(1+firstDigit, digitGrouping);
                    }

                    String intportion = formattedNumber.toString().substring(0, formattedNumber.toString().indexOf("."));
                    if(intportion.length() == 4+firstDigit){
                        formattedNumber.insert(1+firstDigit, digitGrouping);
                    }
                }
            }

            //now remove trailing zeros
            String tmp = formattedNumber.toString();
            int newLength = tmp.length();
            for(int n=tmp.length()-1;n>=0;n--){
                if(tmp.substring(n, n+1).equalsIgnoreCase("0")){
                    newLength--;
                }else{
                    if(tmp.substring(n, n+1).equalsIgnoreCase(".")) newLength--;
                    break;
                }
            }
            formattedNumber.setLength(newLength);

            return formattedNumber.toString();
        }

This doesn't solve the problem of formatting the numbers as the user types, though.


Solution

  • The obvious solution to me is to override the paint method of the EditField so that its displayed as formatted text. The field's getText would return "12345" while it would display as "12 345" for example.

    This is almost perfect for what you need. But you need to remember to handle the cursor drawing as well. You don't want the native cursor on the 5 but drawing on the 3.

    EDIT

    This should help you out. The important parts here are paint and getFocusRect. Please note that this is pretty rough still. It doesn't handle touch events (cursor won't move correctly), and there will be no line wrapping (must be handled by layout and paint methods).

    public class FormattedEditField extends TextField
    {
        public FormattedEditField()
        {
            setFilter(TextFilter.get(TextFilter.NUMERIC));
        }
    
        protected String getFormattedText()
        {
            String text = getText();
            if ((text == null) || (text.length() == 0))
            {
                return "";
            }
            else
            {
                double nr = Double.parseDouble(text);
                return formatNumber(nr, 2, ",");
            }
        }
    
        public synchronized void getFocusRect(XYRect rect)
        {
            // Tell Blackberry where to draw the cursor
            Font font = getFont();
            String formattedText = getFormattedText();
            int length = formattedText.length();
    
            if (length == 0)
            {
                rect.x = getTextOriginX();
                rect.width = font.getAdvance('1');
                rect.y = getTextOriginY();
                rect.height = font.getHeight();
            }
            else
            {
                int cursorPosition = getCursorPosition();
    
                int i;
                for (i = 0; i <= cursorPosition; i++)
                {
                    if (cursorPosition == length)
                    {
                        break;
                    }
    
                    if (formattedText.charAt(i) == ',')
                    {
                        cursorPosition++;
                    }
                }
    
                if (cursorPosition >= length)
                {
                    rect.x = getTextOriginX() + font.getAdvance(formattedText);
                    rect.width = font.getAdvance('1');
                }
                else
                {
                    rect.x = getTextOriginX() + font.getAdvance(formattedText.substring(0, cursorPosition));
                    rect.width = font.getAdvance(formattedText.charAt(cursorPosition));
                }
    
                rect.y = getTextOriginY();
                rect.height = font.getHeight();
            }
        }
    
        protected void drawFocus(Graphics graphics, boolean on)
        {
        }
    
        protected synchronized void paint(Graphics graphics)
        {
            int x = getTextOriginX();
            int y = getTextOriginY();
    
            String text = getFormattedText();
    
            if (isFocus())
            {
                XYRect rect = new XYRect();
                getFocusRect(rect);
    
                int colour = graphics.getColor();
                graphics.setColor(0x207CFE);
                graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
                graphics.setColor(colour);
    
                graphics.drawText(text, x, y);
                graphics.invert(rect);
            }
            else
            {
                graphics.drawText(text, x, y);
            }
        }
    
        // Since we are overriding the painting, we should accommodate the different drawing flags
        private int getTextOriginX()
        {
            int x = 0;
    
            if ((getStyle() & DrawStyle.HCENTER) == DrawStyle.HCENTER)
            {
                x = (getWidth() - getFont().getAdvance(getFormattedText())) / 2;
            }
            else if ((getStyle() & DrawStyle.RIGHT) == DrawStyle.RIGHT)
            {
                x = getWidth() - getFont().getAdvance(getFormattedText());
            }
    
            return x;
        }
    
        // Since we are overriding the painting, we should accommodate the different drawing flags
        private int getTextOriginY()
        {
            int y = 0;
            if ((getStyle() & DrawStyle.VCENTER) == DrawStyle.VCENTER)
            {
                y = (getHeight() - getFont().getHeight()) / 2;
            }
            else if ((getStyle() & DrawStyle.BOTTOM) == DrawStyle.BOTTOM)
            {
                y = getHeight() - getFont().getHeight();
            }
    
            return y;
        }
    
        // This method is from http://supportforums.blackberry.com/t5/Java-Development/Format-a-decimal-number/m-p/763981#M142257
        // I don't know how good it is, since I use my own method for this
        private String formatNumber(double number, int decimals, String digitGrouping)
        {
            Formatter f = new Formatter("en");
            String rawNumber = f.formatNumber(number, decimals + 1);
    
            String rawIntString = rawNumber.substring(0, rawNumber.indexOf(".")); // Basically intString without digit grouping
            StringBuffer intString = new StringBuffer();
            StringBuffer decString = new StringBuffer(rawNumber.substring(rawNumber.indexOf(".") + 1));
            StringBuffer formattedNumber = new StringBuffer();
            int workingVal = 0;
            int newNum = 0;
            boolean roundNext;
    
            // Add digit grouping
            int grouplen = 0;
            int firstDigit;
            if (rawIntString.charAt(0) == '-')
            {
                firstDigit = 1;
            }
            else
            {
                firstDigit = 0;
            }
            for (int n = rawIntString.length() - 1; n >= firstDigit; n--)
            {
                intString.insert(0, rawIntString.substring(n, n + 1));
                grouplen++;
                if (grouplen == 3 && n > firstDigit)
                {
                    intString.insert(0, digitGrouping);
                    grouplen = 0;
                }
            }
    
            // First, check the last digit
            workingVal = Integer.parseInt(String.valueOf(decString.charAt(decString.length() - 1)));
            if (workingVal >= 5)
            {
                roundNext = true;
            }
            else
            {
                roundNext = false;
            }
            // Get the decimal values, round if needed, and add to formatted string buffer
            for (int n = decString.length() - 2; n >= 0; n--)
            {
                workingVal = Integer.parseInt(String.valueOf(decString.charAt(n)));
                if (roundNext == true)
                {
                    newNum = workingVal + 1;
                    if (newNum == 10)
                    {
                        roundNext = true;
                        newNum = 0;
                    }
                    else
                    {
                        roundNext = false;
                    }
                    formattedNumber.insert(0, newNum);
                }
                else
                {
                    formattedNumber.insert(0, workingVal);
                }
            }
            // Now get the integer values, round if needed, and add to formatted string buffer
            formattedNumber.insert(0, ".");
            for (int n = intString.length() - 1; n >= 0; n--)
            {
                try
                {
                    workingVal = Integer.parseInt(String.valueOf(intString.charAt(n)));
                }
                catch (Exception e)
                {
                    formattedNumber.insert(0, intString.charAt(n));
                    continue;
                }
                if (roundNext == true)
                {
                    newNum = workingVal + 1;
                    if (newNum == 10)
                    {
                        roundNext = true;
                        newNum = 0;
                    }
                    else
                    {
                        roundNext = false;
                    }
                    formattedNumber.insert(0, newNum);
                }
                else
                {
                    formattedNumber.insert(0, workingVal);
                }
            }
    
            // Just in case its a number like 9999.99999 (if it rounds right to the end
            if (roundNext == true)
            {
                formattedNumber.insert(0, 1);
            }
    
            // re-add the minus sign if needed
            if (firstDigit == 1)
                formattedNumber.insert(0, rawIntString.charAt(0));
    
            if (digitGrouping.length() > 0)
            {
                if (formattedNumber.toString().indexOf(".") == -1)
                {
                    // no decimal
                    if (formattedNumber.toString().indexOf(digitGrouping) > 3 + firstDigit)
                    {
                        formattedNumber.insert(1 + firstDigit, digitGrouping);
                    }
    
                    if (formattedNumber.toString().length() == 4 + firstDigit)
                    {
                        formattedNumber.insert(1 + firstDigit, digitGrouping);
                    }
                }
                else
                {
                    // no decimal
                    if (formattedNumber.toString().indexOf(digitGrouping) > 3 + firstDigit)
                    {
                        formattedNumber.insert(1 + firstDigit, digitGrouping);
                    }
    
                    String intportion = formattedNumber.toString().substring(0, formattedNumber.toString().indexOf("."));
                    if (intportion.length() == 4 + firstDigit)
                    {
                        formattedNumber.insert(1 + firstDigit, digitGrouping);
                    }
                }
            }
    
            // now remove trailing zeros
            String tmp = formattedNumber.toString();
            int newLength = tmp.length();
            for (int n = tmp.length() - 1; n >= 0; n--)
            {
                if (tmp.substring(n, n + 1).equalsIgnoreCase("0"))
                {
                    newLength--;
                }
                else
                {
                    if (tmp.substring(n, n + 1).equalsIgnoreCase("."))
                        newLength--;
                    break;
                }
            }
            formattedNumber.setLength(newLength);
    
            return formattedNumber.toString();
        }
    }