The absolute beginners guide - Part III - for gnu-smalltalk/gtk (application menu creation)

Tagged:  •    •    •    •  

Today, I'll talk a little bit about menu-creation.

An application does normaly not exist with only one window. You normaly have something to configure your app, or open files etc. These things are triggered through a toolbar or an application-menu.

An application menu constists of a menubar, and the menu-entries with the associated submenus (which of course, can also have submenus). And if the user selects one of the menu-entries, you (or your application) should be informed about this user interaction. For this you want to have some methods, which act - like in the example in Part II - as callback methods.

For playing around with gtk, I decided to use for the first, the following structure:

  • File
    • Open ...
    • Message ...
    • Separator
    • Exit
  • About
    • About...

So, I'm interested in the actions if the user selects Open..., Message..., Exit or About.... For these actions, I create 4 methods, which should act as callbacks.
(And the content of the exit-method should be known from the callbacks in the last Part of this guide...)

openFile [
        'openFile selected' printNl
]

showMessage [
        'showMessage selected' printNl
]

exit [
        window destroy.
        GTK.Gtk mainQuit
]

about [
        'about selected' printNl
]

Ok, but how is a menubar created and the created methods called.

Here I'll offer you 2 possibilities. First the "easy to overview method for really simple menus" and afterwards a more sophisticated solution, which will be better to use for creating large kind of menus.

Important:
|-
|The second possibility is stolen from one of my great source of learning, Gwenaels GTK-Launcher! (And the first one is inspired from it, too...)
Sourcecode used in this examples:

Before we start with the 2 different kinds, I'll make some changes to the current code, which is partly a precondition for both ways.

First, we have to expand our instance variables with 2 new variables. The menubar itself and a kind of container, where we will place the menubar into.

So let's change the instance variables definition to:

        | window container menubar |

In this step, I'll also break the initialize method in several (3) sub-parts. Our previous initialize code goes completely in a method called createWindow and the initialize method will call the methods

  • createWindow,
  • createContainer and
  • createMenubar.

The container is a vertical GTK Box, where the menubar will reside in.

Furthermore, after the 3 components are created, we should pack them together.

     initialize [
             self createWindow.
             self createContainer.
             self createMenubar.

             container packStart: menubar expand: false fill: true padding: 0.
             window add: container.
     ]

     createContainer [
             container := GTK.GtkVBox new: false spacing: 0
     ]

Possibility No. 1

And here now the first method "createMenuBar":

createMenubar [
     | fileMenu aboutMenu menuItem |
     menubar := GTK.GtkMenuBar new.

"-- Create the 'File' menu --"

     fileMenu := GTK.GtkMenu new.
     menuItem := GTK.GtkMenuItem newWithLabel: 'Open...'.
     menuItem connectSignal: 'activate' to: self selector: #openFile userData: nil.
     fileMenu append: menuItem.

     menuItem := GTK.GtkMenuItem newWithLabel: 'Message...'.
     menuItem connectSignal: 'activate' to: self selector: #showMessage userData: nil.
     fileMenu append: menuItem.

     fileMenu append: GTK.GtkMenuItem new. "This is a separator"

     menuItem := GTK.GtkMenuItem newWithLabel: 'Exit'.
     menuItem connectSignal: 'activate' to: self selector: #exit userData: nil.
     fileMenu append: menuItem.

"-- and click the file-menu into the menubar! --"

     menuItem := GTK.GtkMenuItem newWithLabel: 'File'.
     menuItem setSubmenu: fileMenu.
     menubar append: menuItem.

"-- create the about menu --"

     aboutMenu := GTK.GtkMenu new.
     menuItem := GTK.GtkMenuItem newWithLabel: 'About...'.
     menuItem connectSignal: 'activate' to: self selector: #about userData: nil.
     aboutMenu append: menuItem.

"-- and click the about-menu into the menubar! --"

     menuItem := GTK.GtkMenuItem newWithLabel: 'About'.
     menuItem setSubmenu: aboutMenu.
     menubar append: menuItem.
]

First, we create the instance of the menubar.
Then the fileMenu with all the items in it. The name of the file-menu is associated with a new MenuItem, which is associated a little bit later. I think, this code is pretty self-explanatory...

Possibility No. 2

This one is a bit more complicated, but has the power that you could easily enhance it. Just a few more array entries and a few callback-methods and you're done. But have a look:

myCreateMainMenu: anArray [
        anArray do: [:each |
                menubar append: ((GTK.GtkMenuItem newWithLabel: each first) 
                                       setSubmenu: (self perform: each second))]
]

myCreateMenuEntry: anArray [
        | menu |

        menu := GTK.GtkMenu new.
        anArray do: [:each | 
                menu append: 
                     (each isEmpty
                           ifTrue: [GTK.GtkMenuItem new]
                           ifFalse: [(GTK.GtkMenuItem newWithLabel: (each at:1))
                                                connectSignal: 'activate' 
                                                           to: self 
                                                     selector: (each at: 2) 
                                                     userData: nil;
                                     yourself]
                     )
        ].
        ^menu
]

createFileMenu [
        ^self myCreateMenuEntry: {
                #('Example1' #example).
                #('Example2' #example2).
                #().
                #('Exit' #exit)
        }
]

createAboutMenu [
        ^self myCreateMenuEntry: {
                #('About...' #about)
        }
]

createMenubar [
        menubar := GTK.GtkMenuBar new.

        self myCreateMainMenu: {
                #('File' #createFileMenu). 
                #('About' #createAboutMenu)
        }
]

Both my* Methods

  • myCreateMainMenu and
  • myCreateMenuEntry

(The naming isn't quite correct, but I want to mark the difference in the method-name)

could be moved for instance in a superclass (like it is done in GTK-Launcher) and then you have only this clean code of createMenubar, createFileMenu and createAboutMenu.
You create only arrays with the MenuItem label and the selector (of course, you have to provide the methods! ;-) ) and that's it.

I think, the technicque here used is more for the intermediate learner with code-blocks and arrays and (perform! -> look therefore in the Object-class) but it has its attraction (in my opinion).

And here a short description of what's going on there (if I'm the one who would explain that...)

The only method which is called from our code is createMenubar. In this method, we create the menubar itself and create an array with the entries which should come up in the menubar and for each entry a selector, where the method for this selector creates the menuitems for the specified menubar entry.
Each of these methods itself defines even only an array and the method myCreateMenuEntry creates than the single menus.

Important:
|-
|Please look into the GtkWindow and GtkLauncher classes of the GTK-Launcher application of Gwenael!
For a more detailed enhanced example:

That's all for today...

Happy coding!

Joachim.

P.S.: tomorrow - I think - it's time again, to show one more time the complete source-code of the current status...

User login