If you have already read this article you may wonder: “Ok, I can do a Hello World application with some new framework, so now what?”.
“Hello World” applications are not usually very good examples of real world applications, they are just there to get you started. However no one would ever write a tutorial using a real (and I mean REAL) world application. What would be the better example to demonstrate the possibilities of a given framework is an endless debate.
For this tutorial I chose to build a simple calculator. Simple because it will not have many of the features other calculators have. Simple because it should be easy to write, and of course, because it should fit this tutorial. However it should also have enough features so that it could show most of the RuGUI features.
RuGUI has evolved a little bit since I’ve written the first tutorial, for example, now we could have chosen to write this application using the Qt framework. For now we will stay with the Gtk framework, later I will translate this tutorial for Qt users.
The full application source code can be downloaded here
Talking about MVC
First, let me explain how the MVC pattern is used in RuGUI. RuGUI was initially inspired by the pygtkmvc framework. Some of the features that come with RuGUI, like the Observer pattern, implementation were inspired by pygtkmvc.
The idea behind using the observer pattern for model->controller interactions is great, so why didn’t we just use that framework? Well, it had some limitations at that time. You could have only one model, one view and one controller. I’ve seen that this has changed, and now you can have a different design for your controllers/views/models. Still, we were using ruby for our web applications and we wanted to continue to use it for the desktop applications.
RuGUI lets you design your controllers/views/models in any way you want. The principles are maintained, a model can be observed by controllers, and a controller may control any number of views, but also other controllers. The only “limitation” is that you may only have one MainController, but well, how could you have more than one?
Enought of this talk, lets code something!
First, lets generate the application, go to your projects directory and type:
rugui simple_calculator
If you run the application (rake run
or ./bin/simple_calculator
) you will see the “Hello World” application.
We will stick with only one view for now, so it will have everything in it. It is pretty simple, a TextEntry above, to display the results. We could have used a Label, but the TextEntry is more powerful, and it is already styled for us. We just set it to not be editable, so that the users can only enter values using the buttons. We called it display
; this will be used later to reference it in code.
Below the display, we have the input buttons. The numerical ones were called as num_x_button
where x
is the numerical value of the button. The operators were called xxx_operation
where xxx
is one of the available operations (sum, subtraction, division, multiplication). Finally, the backspace, equals, decimal separator, and clear buttons were called backspace_button
, equals_button
, decimal_button
, and clear_button
respectively.
A vertical layout and a table layout were used to pack the widgets together. Notice that we will not set signal handlers in glade.
Ok, the view is ready (at least the glade file), so lets go to the model. What do we need?
- We need to keep track of which numerical keys were pressed.
- The decimal separator button could cause us some trouble…
- We will only be performing simple operations, so that after a second operator is pressed it takes the result of the first operation as its first parameter
- For example: The user presses 4, then +, then 7, then *. In this moment we should have performed the first operation (4 + 7).
- We have a backspace button, which clears the last digit (only digits)
- We have the clear button, which clears it all.
We could model this as a Calculator model class with these observable properties:
- numerical_keys: An array with all the numerical keys and, possible a single decimal separator.
- operand: Saves the last operand after one of the operators are clicked.
- operation: Saves the last operator clicked.
- result: Saves the result of the last operation.
We generate the model file:
script/generate model Calculator
The observable properties in the Calculator class should look like this:
observable_property :numerical_keys, :initial_value => []
observable_property perand
observable_property peration
observable_property :result
But who is controlling it all?
Since this is a simple application the MainController itself will be controlling everything, ahem, I mean, almost everything. One of the main problems I usually find in MVC application is the misuse of the models as “inteligent beings”. See, if someone was to know how to calculate the result, for example, that would be the model. By using the model we simplify the implementation and also allow it to be tested separately.
Lets take a look at the controller implementation, step by step:
def setup_views
register_view :main_view
end
def setup_models
register_model :calculator
end
Here we register the MainView and the Calculator model, which we are going to use for this application.
on :backspace_button, 'clicked' do
self.calculator.backspace
end
on :clear_button, 'clicked' do
self.calculator.clear
end
on :equals_button, 'clicked' do
self.calculator.calculate_result
end
Ahm??? Get it? Here we are actually telling that for any view which has a button named :backspace_button
that is clicked, we want to do something. In this case we are calling a method in the model. We could have done it in the older way, by setting a method handler in glade and declaring it here, like so:
def on_backspace_button_clicked(widget)
self.calculator.backspace
end
def on_clear_button_clicked(widget)
self.calculator.clear
end
def on_equals_button_clicked(widget)
self.calculator.calculate_result
end
However, there are some disadvantages in the latter wat. First you have to declare the method in a glade file and also in your code. Second, the method must contain all arguments needed for the handler, even if you don’t intend to use it. Finally, this would only work if you have created the widget in a glade file. For all these reasons the latter form is somewhat deprecated, it will still work, but you should prefer the first form.
on :main_window, 'delete-event' do
quit
end
When the main_window is close we quit the application.
(0..9).each do |numerical_key|
on "num_#{numerical_key}_button", 'clicked' do |widget|
key = widget.name.match(/num_(\d)_button/)[1]
self.calculator.numerical_keys << key
end
end
on :decimal_separator_button, 'clicked' do
self.calculator.numerical_keys << '.' unless self.calculator.numerical_keys.include?('.')
end
%w(sum subtraction multiplication division).each do |operation|
on "#{operation}_operation_button", 'clicked' do |widget|
operation = widget.name.match(/([a-z]*)_operation_button/)[1]
self.calculator.change_operation_to(operation)
end
end
Wew!!? What is this? Well, a little bit of meta-programming can do no harm, can it? I could have repeated this for each numerical key, but it just does not make any sense. Surely if I needed some performance I wouldn’t be doing this, but, hey this is a simple calculator! Let us use the power of Ruby language at our favor!
def property_numerical_keys_changed(model, new_value, old_value)
self.main_view.display.text = new_value.join
end
def property_result_changed(model, new_value, old_value)
self.main_view.display.text = new_value.to_s
end
Finally we are actually messing with the MainView. But, just two methods? Yes, you are right, there are just two methods that changes the view. One is called every time the numerical_keys property is changed, basically, whenever the user clicks on a key button, but it can also happen when calculating the result. The other is called after the result changes, i.e., after it is calculated.
Is there any logic in that?
Almost forgot it… Yep, here it is (in the Calculator model, of course):
def change_operation_to(operation)
if has_operand?
calculate_result
else
save_operand
end
self.numerical_keys = []
self.operation = operation
end
def has_operand?
not self.operand.blank?
end
def save_operand
self.operand = Calculator.to_operand(self.numerical_keys)
end
The first method changes the current operation. If we have an operand, the we calculate the result, which will left us with only one operand, then we clear the numerical_keys and change the operation. If we don’t have an operand yet, we just save it, then clear the numerical_keys and change the operation. The save_operand method calls Calculator.to_operand(numerical_keys_array)
, which just joins the array (remember that the input keys are strings, not integers) and transform it to a float.
def backspace
self.numerical_keys.pop
end
def clear
reset!
end
These two are very simple, the first one just pops the last key from the numerical_keys property (it has no effect if the array is empty). The second calls a very useful (if used with extreme care) method of RuGUI::BaseModel
, the reset!
method. It will reset all observable properties of the model instance to their reset_value, which are, by default, equals to their initial_value.
def calculate_result
if self.numerical_keys.empty?
# we don't have a second operand, so there is nothing to be calculated.
self.result = self.operand
elsif self.operand.blank?
# no operand, nothing to do.
else
second_operand = Calculator.to_operand(self.numerical_keys)
self.numerical_keys = []
send(&quot;calculate_#{self.operation}_result&quot;, second_operand)
self.operand = self.result
end
end
def calculate_sum_result(second_operand)
self.result = self.operand + second_operand
end
def calculate_subtraction_result(second_operand)
self.result = self.operand - second_operand
end
def calculate_multiplication_result(second_operand)
self.result = self.operand * second_operand
end
def calculate_division_result(second_operand)
self.result = self.operand / second_operand
end
Well this is the most complicated one. Here are the scenarios it is covering:
- When we have no numerical_keys we just set the result equals to the operand. That enables us to let the users click in two operators sequentially to change the current operator.
- When there is no operand there is nothing to calculate.
- Otherwise we get the second operand, clear the numerical_keys (this will make the visor be cleared), then call the proper calculation method, given the current operation.
That’s it!
Try this awesome simple calculator now! We’ve made it with only a few lines of code (less than 200 for the model, view, controller). It is still missing some usability and, better displaying of operations, not to say that it is very limited, but it served well for this tutorial.
This tutorial was written using another RuGUI application: Markup Translator. It allows you to write in textile and see the HTML output on-the-fly side by side. It is still a work in progress, but feel free to check it out and explore its code!
If you want to contribute for RuGUI, or if you have written an application using RuGUI, please let us know!
Tagged gtk, ruby, RuGUI, tutorial