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!
{ 5 } Comments
interessante trabalho com o Model e o Controller,mas eu tenho uma duvida na view:
Como eu posso trabalhar com + de uma view e + de um arquivo glade?
now i see…I should speak in english…. Sorry
How can I work with more than one view and more than one glade file?
I mean,how can I create a view,and open that like a popup window without using the GTK code and using the view like a new window?
Hi Nathan,
Yes, it would be better if we speak in english, so that others may follow us.
Regarding your question, you may take a look at the markup_translator, which has an about dialog that works just like that. Here is an example:
./script/generate view my_dialog_view
This will create a view named MyDialogView, with a glade file my_dialog_view.glade. Just open the glade file in glade3 and setup your widgets there. You may use one of the dialog widgets (which inherits from Gtk::Dialog). Then, register this view in the MainController (or in other controller if you want), and add code to open the dialog (here it will open when the hello_button is clicked):
class MainController < RuGUI::BaseController
def setup_views
register_view :main_view
register_view :my_dialog_view
end
on :hello_button, 'clicked' do
response = self.my_dialog_view.dialog.run
# do something with the response here
end
end
That´s it, questions are always welcome.
can i make app as a gnome-panel applet? and how?
Hi,
I really haven’t had the need to create such applications yet. The insteresting thing about RuGUI is that it doesn’t really add limits to what you can do. Anything you can do with Ruby/GTK (a.k.a, Ruby/Gnome) you can do with RuGUI, only you will need to write less code, and you code will be more organized.
I’ve found this tutorial in python, but I couldn’t found an simple example in Ruby. In the Ruby/Gnome site there is this page, but it seemed so complex that I cannot believe it was designed for Ruby, which have always a simple way to do things. If you have had any experience building gnome-panel applets in Ruby/GTK, please share with us.
Cheers,
Vicente
Post a Comment