require 'tk' require 'enumerator' class Calculator def initialize() @number_buttons = [] #references of the buttons 0-9 @operand_buttons = [] #references of the operand buttons @current_operand = "" #the operand currently typed in @second_operand = "x" #the second operand in the case of a binary operation @binary_operators = {'*' => 'asterisk', '/' => 'slash', '+' => 'plus', '-' => 'minus', '%' => 'percent'} @unary_operators = { 'x*x' => "operand ** 2", 'sqrt' => "Math.sqrt(operand)" , 'sin' => "Math.sin(operand)", 'fact' => "factorial(operand)"} @root = TkRoot.new("geometry"=>"345x220") { title "Scientific calculator!" } @result_ready = false setup_number_buttons setup_operand_buttons setup_display bind_events Tk.mainloop end def setup_number_buttons @buttons_frame = TkFrame.new(@root).pack("side"=>"bottom") (1..9).to_a.each_slice(3) do |s| s.each {|n| @number_buttons << TkButton.new(@buttons_frame, "text"=> n.to_s) } TkGrid.grid(*@number_buttons.slice(s[0]-1..s[2]-1)) end @number_buttons.unshift TkButton.new(@buttons_frame, "text"=> "0") @number_buttons[0].grid("columnspan"=>3, "sticky"=>"ew") TkGrid.configure(*@number_buttons) end def setup_display @display_frame = TkFrame.new(@root).pack("side"=>"top") @display = TkText.new(@display_frame, 'background'=>"#FFF4C1").pack() @display.font(TkFont.new('arial').configure('size'=>18)) end def setup_operand_buttons all_operators = @binary_operators.keys + @unary_operators.keys @c_button = TkButton.new(@buttons_frame, "text"=> "C") @c_button.grid("row"=>0, "column"=>3, "rowspan"=>2, "sticky"=>"ns") (0..8).to_a.each_slice(3) do |s| s.each { |n| @operand_buttons << TkButton.new(@buttons_frame, "text"=> all_operators[n]).grid("row"=>n % 3, "column"=>3+(s[2]+1)/3, "sticky"=>"ew") } end @equal_button = TkButton.new(@buttons_frame, "text"=> "=") @equal_button.grid("row"=>3, "column"=>4, "columnspan"=>3, "sticky"=>"ew") end def bind_events @number_buttons.each do |button| @root.bind("Any-Key-#{button.text}") { update_display(button) } end @operand_buttons.each do |button| keysym = @binary_operators[button.text] @root.bind("Shift-Key-#{keysym}") { update_display(button) } unless keysym == nil end @root.bind("Any-Key-equal") { calculate } @root.bind("Any-Button-1") {|event| update_display(event.widget)} end def update_display(target) return if !target.kind_of? TkButton #so that clicks on a non-button widget is not intercepted text = target.text if (text == '=') calculate elsif (text == 'C') clear_display elsif (@unary_operators.include? text) @display.clear @display.insert("end", result = "#{eval(@unary_operators[text].sub(/operand/, @current_operand.to_s ))}") @current_operand = result.to_s elsif (@binary_operators.include? text) @result_ready = false @current_operator = target.text @second_operand = "" else @display.clear if (@current_operand == "") || @result_ready if @result_ready @result_ready = false @current_operand = "" end if (@second_operand == 'x') @current_operand += target.text elsif (@second_operand == '') @display.clear @second_operand += target.text else @second_operand += target.text end @display.insert('end', target.text) end end #Thanks to James Edward Gray II - http://blog.grayproductions.net/articles/2006/01/17/recursion-and-callbacks def factorial( number ) (1..number).inject { |prod, n| prod * n } end def calculate @display.clear @display.insert('end', result=eval("#{@current_operand} #{@current_operator} #{@second_operand}")) @current_operand = result.to_s @second_operand = "x" @result_ready = true end def clear_display @result_ready = false @current_operand = "" @second_operand = "x" @display.clear end end Calculator.new