Calculator - Step 2.5 Processing Keyboard Inputs

Objective for this step

In this step, we will handle keyboard inputs to the calculator.

The Code

Note: If you have installed php-gtk2 using Gnope Installer on Windows, and if running the sample code below gives you warning that the Symbolic names for keys (e.g. Gdk::KEY_Return) is not defined, you might want to update your php-gtk2 with the latest php-gtk2.dll available here. Simply download the php-gtk2.dll and replace the copy in the folder php-gtk2\ext. The latest compilation has put in the Symbolic names for keys listed here.

1   
2   
3   
4   
5   
6   
7   
8   
9   
10   
11   
12   
13   
14   
15   
16   
17   
18   
19   
20   
21   
22   
24   
25   
26   
27   
28   
29   
30   
31   
32   
33   
37   
38   
39   
45   
49   
50   
51   
52   
53   
54   
55   
56   
57   
58   
59   
60   
61   
62   
63   
64   
65   
66   
67   
68   
70   
71   
72   
73   
74   
75   
76   
77   
78   
79   
80   
81   
82   
83   
86   
87   
88   
89   
94   
95   
96   
97   
98   
99   
100   
101   
103   
104   
105   
106   
107   
108   
109   
110   
111   
112   
113   
115   
116   
117   
118   
119   
120   
121   
122   
123   
124   
125   
126   
127   
128   
129   
130   
131   
132   
133   
135   
136   
137   
138   
139   
140   
141   
142   
143   
144   
145   
146   
147   
148   
149   
150   
151   
152   
153   
154   
155   
156   
157   
158   
159   
160   
161   
162   
163   
164   
165   
166   
167   
168   
169   
170   
171   
172   
173   
174   
175   
176   
177   
178   
179   
180   
181   
<?php

class Calculator{

    var $operator = '';
    var $stack = array();
    var $is_entering_number = 0;

    // constructor
    function Calculator() {
    }

    function main() {
        $this->setup_window();
        $this->setup_layout();
        $this->window->show_all(); // display the calculator
        Gtk::main(); // and let's go!
    }

    function setup_window() {
        $this->window = new GtkWindow();
        $this->window->set_size_request(200, 240);
        $this->window->connect_simple('destroy', array('Gtk','main_quit'));
        $this->window->connect('key-press-event', array(&$this, "on_keypress"));
    }

    function setup_layout() {
        // setup a vbox to hold display and buttons
        $vbox = new GtkVBox();
        $this->window->add($vbox);

        // setup display
        $this->display = new GtkEntry();
        $this->display->set_alignment(1.0); // right-justified
        $this->display->set_editable(false); // for display only
        $vbox->pack_start($this->display);

        // the button labels
        $button_label = array(
        array('BackSpc', 'CE', 'C'),
        array('7', '8', '9', '/', 'sqrt'),
        array('4', '5', '6', '*', '%'),
        array('1', '2', '3', '-', '1/x'),
        array('0', '+/-', '.', '+', '=')
        );

        // setup the buttons
        for ($j=0; $j<5; ++$j) {
            $hbox = new GtkHBox();
            $vbox->pack_start($hbox); // use a hbox to hold each row of buttons
            for ($i=0; $i<5; ++$i) {
                if ($j==0 && $i>2) continue; // first row contains only 3 buttons
                $button = new GtkButton($button_label[$j][$i]);
                $button->set_size_request(40, 32); // set the size
                $button->connect('clicked', array(&$this, "on_button"));
                $hbox->pack_start($button);
            }
        }
    }

    // process button click
    function on_button($button) { // get the button input and call process_input()
        $value = $button->child->get_text(); // get the button value
        echo "button_click: $value\n";
        $this->process_input($value); // process the input
    }

    // process keyboard input
    function on_keypress($widget, $event) {// get keyboard input and call process_input()
        $keyval = $event->keyval; // get the keypress value
        $value = chr($keyval); // get the ASCII equivalent
        if (preg_match("/[0-9\+\-\*\/\.=]/", $value) || $keyval==Gdk::KEY_Return || $keyval==Gdk::KEY_BackSpace) {
            if ($keyval==Gdk::KEY_Return) $value='=';
            if ($keyval==Gdk::KEY_BackSpace) $value='BackSpc';
            $this->process_input($value);
            $this->display->set_position(-1); // move the cursor to the end of display
        }
    }

    // process input
    function process_input($value) {
        if (preg_match("/^([0-9]|\.)$/", $value)) {
            $number = array_pop($this->stack);
            if (preg_match("/^([0-9]|\.)+$/", $number)) {
                $number .= $value;
            } else {
                if ($number!='') array_push($this->stack, $number);
                $number = $value;
            }
            array_push($this->stack, $number);
            $this->display->set_text($number);

        } elseif (preg_match("/^(\+|-|\*|\/|=){1}$/", $value)) { // perform binary operations
            if (count($this->stack)<3) {
                array_push($this->stack, $value);
            } else {
                $number2 = array_pop($this->stack);
                $operator = array_pop($this->stack);
                $number1 = array_pop($this->stack);
                switch ($operator) {
                    case '+': $result = $number1 + $number2; break;
                    case '-': $result = $number1 - $number2; break;
                    case '*': $result = $number1 * $number2; break;
                    case '/': $result = $number1 / $number2; break;
                }
                $this->display->set_text($result);
                array_push($this->stack, $result);
                if ($value!='=') array_push($this->stack, $value);
            }

        //process unary operators
        } elseif ($value=='1/x' || $value=='sqrt' || $value=='%' || $value=='+/-') {
            if (count($this->stack)>=1) {
                $number = array_pop($this->stack);
                switch ($value) {
                    case '1/x': $result = 1/$number; break;
                    case 'sqrt': $result = sqrt($number); break;
                    case '%': $result = $number / 100; break;
                    case '+/-': $result = -$number; break;
                }
                $this->display->set_text($result);
                array_push($this->stack, $result);
            }

        } elseif ($value=='C') {
            $this->stack=array();
            $this->display->set_text('0');

        } elseif ($value=='CE') {
            if (preg_match("/^([0-9]|\.)+$/", end($this->stack))) {
                $number = array_pop($this->stack);
                $this->display->set_text('0');
            }

        } elseif ($value=='BackSpc') {
            if (preg_match("/^([0-9]|\.)+$/", end($this->stack))) {
                $number = array_pop($this->stack);
                $number = substr($number, 0, strlen($number)-1);
                $this->display->set_text($number);
                array_push($this->stack, $number);
            }
        }
        $this->show_stack(); // show current stack
    }

    // prints the current stack content
    function show_stack() {
        echo "current stack content:\n";
        for ($i=count($this->stack)-1; $i>=0; --$i) {
            echo "stack[$i] = {$this->stack[$i]}\n";
        }
        echo "\n";
    }
}

$cal = new Calculator(); // cerate a new calculator
$cal->main(); // let's run it!

?>
 

Explanation

Since the processing of keyboard input is exactly the same as that of the buttons, we can reuse the entire code by extracting the processing of input into a function process_input($value), and calling this function from the callback functions of button and keypress.

What's Next

We have put in the core business logic of the calculator. It's a fully functional calculator now.

However, we have not put in any error checking yet, for example, if user enters two decimal points (1.2.3), or keys in 6*=.

We will put in the validation checks in the next step.

Links

Search This Site

Google
Web This Site

Search PHP-GTK2 Manual

Full-text search on php-gtk2 manual

Members Login

Username:
Password:
Key:
What is this?
  Forget Password?