Saturday 13 September 2008

Ruby GUI

I have been looking at how easy it would be to put a GUI front-end on Ruby. Ruby has no native support for a GUI (one place Java and C# really win over Ruby), and while Rails has become the standard for web aplications, there are numerous options for a GUI plug-in.

I have had a look at Tk, Fox and Swing (for JRuby). Recently, however, I found Ruby Shoes. Shoes is a very simple GUI, but that is part of its appeal. I doubt it has the comprehensive range of widgets that Tk does, for instance, but it can cope with JPEGs, which Tk cannot (as far as I could find). Also Shoes is dead easy to use:. Download. Install. Start the Shoes application, point it at your ruby file (or one of the samples included). Your application is runnning.

Unlike most other GUI toolkits, Shoes is not just a Ruby front end to an existing kit, which seems to make it feel more Ruby-like.

However, the big problem with Shoes is that there is no menu support incorporated. See this forum thread for a discussion on that:
http://www.ruby-forum.com/topic/165398

Find Shoes here:
http://code.whytheluckystiff.net/shoes/

My Quick Guide to Shoes

Everything in Shoes goes inside a Shoes.app block (usually, anyway). The simplest Shoes application is therefore

Shoes.app do
end


You can put in some options at this point - in a hash of course.

Shoes.app :title => "A Great Application", :width => 400, :height => 600, :resizable => false do

As usual, your require statements go at the very top of the file. Inside the Shoes.app block you can place the code that determines the GUI format and your methods.


Widgets are laid out inside either of two layouts, stacks for vertical stacking, and flows for horizonal flowing widgets. They can be nested for complex interfaces.

flow do
end

stack :margin_left => 5, :margin_right => 10, :width => 1.0, :height => 200, :scroll => true do
end


Note that the width in the second example is 1.0, i.e., 100%. You can assign the layout to a class variable, and then manipulate it later in a method for dynamic interfaces.

@gui_completed = stack
@gui_completed.clear


Flow and stack are not analogous. In the flow layouts, components fill a line, then go on to the next line, while in a stack, it is strickly one above the other. Shoes does not like horizontally scrollbars (neither do I). Sometimes components will stretch to fill the available room, and if they do that for a flow, they end up on the next line, and it looks more like a stack. The solution is to specify widths.

You can also use absolute positioning with :left and :top.

The background method set the background. If this is inside a layout, the background for the layout is set. The border method works the same

background white
background "back.jpg", :height => 40 # Set the top 40 pixels tan
border blue, :strokewidth => 5
background "#000".."#FFF", :curve => 15 # Gradient filled, black at the top
background "#000".."#FFF", :curve => 15, :angle => 90 # Gradient rotated 90 degrees anti-clockwise


You can use :width, :height, :right, :bottom to put different backgrounds along a certain side or corner. The :curve option should give your panel rounded corners, but not if you specify a border (note that the KNS manual says :radius; this is out of date).

You can manipulate your panels.

@my_stack.clear
@my_stack.clear { add_new_stuff }
@my_stack.append { add_new_stuff }
@my_stack.prepend { add_new_stuff }
@my_stack.before existing_component { add_new_stuff }
@my_stack.after existing_component { add_new_stuff }
@my_stack.remove existing_component
@my_stack.hide
@my_stack.show


Text can be done with: banner, title, subtitle, tagline, caption, para, inscription (in descending order of size).

title 'Here is my Application'
para 'some simple text'
caption "A caption, in red", :margin => 8
para strong('Some text in bold (like caption)')
para "Some fancy text", :stroke => red, :fill => yellow, :font => "Monospace 12px"


As well as accepting a string, these methods will also take arrays. You can use that to put in formatting within a line. In the second example, a link is created (looking as on a web page); click on it and the do_stuff method is invoked.

para ['Some text with ', strong('this'), ' in bold']
capture 'Please click ', link('here') { do_stuff }, '.' # Square brackets are optional


Options include strong, em, code, del, ins, link, span, sub, sup. It does not seem to cope well with characters outside the standard ASCII set. You can dynamically change content with the replace method.

You can change the style of a link (for the whole application).
style(Link, :underline => false, :stroke => 'red')

Text boxes can be done with edit_line or edit_box. The second example has some default text. The block gets invoked each time the text box is used, so @note will always have the text currently in the text box. How easy is that?

@text1 = edit_line :margin_left => 10, :width => 180
@text2 = edit_box "Default text", :width => 1.0, :height => 200, :margin_bottom => 20 do
@note = @text2.text
end


Buttons are easy too. When the button is pressed, the code in the block is invoked.

button("Add", :margin_left => 5) { add_todo(@add.text); @add.text = '' }
button "Swap" do
swap
end


There are some built in functions for dialog boxes:

ask("What is your name?")
confirm("Would you like to proceed?")
ask_open_file
ask_save_file
ask_open_folder
ask_save_folder
ask_color("Pick a Color")


Most components can be moved and resized

@comp.move(x, y)
@comp.size(w, h)


You can capture mouse movement like this:

motion do x, y
@o.move width - x, height - y
end


click do button, x, y
# button is 1for left button, 2 for right
end


Images are very easy, just use image, with the path to your image (plus style optins as required). Images can be changed on the fly by setting the path attribute (but why not have a replace method like there is for text elements?).

@little_image = image 'picture.jpg', :width => 50, :height => 50
@little_image.path = 'alternative.jpg'


You can even access the clipboard:

self.clipboard = ""

Shoes supports dropdown lists (list_box), checkboxes (check) and radio buttons (radio). see the manual included with the download for details. The big omission is menus, as mentioned earlier, but hopefully this will be rectified by the end of the year.

See also:
http://hackety.org/2008/06/12/martinDemellosGooeyChallenge.html
http://www.infoq.com/news/2007/09/ruby-shoes

Struggling with Ruby: Contents Page

Wednesday 3 September 2008

Ruby Hashes

Ruby uses hashes a lot to pass data around, and one reason for that is that they are just so easy to use. A hash is a object that contains a number of name-value pairs; a dictionary. A hash with a single entry is created like this:
h = {name1 => value1}

In practice, the name is usually a symbol, and the value often a string, so this would be the usual format:
h = {:name1 => 'value1'}

For multiple entries, separate with commas.
h = { :name1 => 'value1', :name2 => 'value2', :name3 => 'value3' }

In method calls the curly brackets seem not to be required or even desired. This can make it seem as though the method takes several parameters rather than a single hash; I think that is deliberate.

New name-value pairs can be added
h[:newname] = 'new value'
h.store :newname, 'new value'


Values can be accessed like an array:
value = h[:name1]
h[:name1] = 'new value'
h[:name1] = nil

Some web sites suggest the last of these will delete the entry; not so! To delete, use the delete method:
h.delete(:mykey)


However, accessing the key with the value is not trivial (as far as I can see). Here are two options:
h.invert[:value1]
h.find{ |k,v| == :value1 }[0]


To determine if a hash has a certain key, do this:
h.key? :my_key


You can loop through a hash with the each method. This example outputs each name-value pair (I believe the order is arbitrary).
h.each { |x,y| puts "Name #{x} Value #{y}" }

The delete_if method iterates through the hash and removes any entry for which the block evaluates to true. Note that as with the delete_if method in Aaray, this does not conform to the convention of an explanation mark for methoids that change the original.

Useful article:
http://www.informit.com/articles/article.aspx?p=26943&seqNum=3

By the way, Rails uses HashWithIndifferentAccess, which is special as it allows strings and symbols to be used interchangeably as keys when accessing values.


Struggling with Ruby: Contents Page