Blitz:Events

From Amiga Coding
Jump to: navigation, search

Intuition uses an event system to notify programs of any GUI actions they need to be aware of, such as a gadget being clicked or a menu item being selected. These notifications are passed to programs as a message, which the program then decodes and decides what to do based on the message. Blitz decodes these messages for you and provides some simple commands for figuring out what happened so your program can respond.

Typically, once the interface is set up, the program flow for dealing with events will be something like this:

  • Wait for an event message to arrive
  • Check the event flags
  • Check the individual item that caused the event
  • Carry out the desired action
  • Repeat


Note that the AmiBlitz Intuition include is recommended over these internal commands if you're developing using AmiBlitz 3.

Event Flags

Events are identified by special values called IDCMP flags, which are defined by the operating system. Each flag corresponds to a type of event, e.g. menu selected, close gadget clicked, key pressed. All the flags are defined in the Intuition Autodocs, however a window will only report the flags it has been set up to look for. All the common flags are configured for windows in Blitz by default, and the list of flags reported by a window can be modified if required by your program. Many of the Blitz Basic example programs use the actual values of the flags to check for events. The use of constants is advisable however, and will make your code easier to follow later on. The Intuition contents are defined in the amigalibs.res resident file, which can be added to the Resident Files list in the Complier Settings window to include it in your code. Don't forget to add a hash symbol (#) before the constant name from the Autodocs to make it a valid Blitz constant! For example, the C constant IDCMP_MENUPICK becomes #IDCMP_MENUPICK.

Reading Events

Events are entered into a queue automatically by Intuition. Two commands are used to read the first event flag from the queue: Event and WaitEvent. Event checks the queue and returns the flag of the first event waiting. If there are no events waiting (i.e. nothing happened), Event will return 0. WaitEvent works similarly, except program flow will stop until an event does occur.

WaitEvent has the advantage that your program sleeps while it waits and uses no CPU time, and is the preferred method for handling GUI input from the user. The disadvantage is that your program can't do anything else while it waits, so if you need to do something without user interaction (for example animation or enemy movements), use Event instead.

Event flags are long values, so always use a long variable for storing the result of Event or WaitEvent!

Examples of usage:

ev.l = Event
If ev
  NPrint "Event flag: ", ev
Else
  NPrint "No event in queue"
End If
ev.l = WaitEvent
NPrint "Event flag: ", ev

Identifying the Event Type

The event flag received in the examples above can be examined to determine what type of event occurred. It will contain the value of the IDCMP flag corresponding to the event type. A simple way to deal with different event types is a Case-Select block:

Select ev
  Case #IDCMP_GADGETUP
    NPrint "Gadget has been clicked and released"
  Case #IDCMP_MENUPICK
    NPrint "A menu item was selected"
  Case #IDCMP_CLOSEWINDOW
    NPrint "Window close gadget clicked"
End Select

Reading the Event Code

Once an event is received, the event code can be read with the EventCode statement. This code contains values specific to the event, such as the mouse button details that triggered a mouse click event, or the entry selected when a listview gadget was clicked. Not every event type requires the event code to determine what happened - the documentation for the particular gadget or event will let you know what details can be obtained and by which method. An example of using event code is as follows:

ev.l = WaitEvent
ec.l = EventCode

Identifying the Event Source

Once you see the type of event you want to deal with, you need to decide what to do. For gadgets and menus, you need to identify the individual item that triggered the event since the IDCMP flag just says "a gadget was clicked". This is done differently depending on the type of event.

Gadget Clicked

If the event flag corresponds to a gadget being clicked or released, the GadgetHit function returns the ID code of the gadget affected. For example:

gh = GadgetHit
Select gh
  Case 0
    NPrint "Cancel gadget clicked!"
  Case 1
    NPrint "Continue gadget clicked!"
End Select

Again, many of the old Blitz examples use "magic numbers" for gadget IDs, but your code will be much more readable if you use constants instead. These should be defined in your code before they're used. Reworking the above example:

gh = GadgetHit
Select gh
  Case #cancel
    NPrint "Cancel gadget clicked!"
  Case #continue
    NPrint "Continue gadget clicked!"
End Select

Practically all gadgets produce a #IDCMP_GADGETUP event when clicked: buttons, checkboxes, listviews, radio buttons and string gadgets all produce gadget up messages (be careful though - string gadgets only produce the message when pressing Enter, not when clicking elsewhere after entering text).

Menu Item Selected

If the event flag corresponds to a menu item being selected, the MenuHit, ItemHit and SubHit commands return the ID of the menu, item, and subitem (if relevant) respectively. Since menu positions are defined by their ID number, this means that MenuHit returns 0 for the first menu, 1 for the second and so on. Similarly, ItemHit will return 0 for the first item in the chosen menu, 1 for the second, and the same for SubHit. Constants are still recommended for identifying menu items of course. As well as making your code easier to read, constants will also make it much easier to rearrange the menu items later on without having to search your code to update the values. Example code:

Select MenuHit
  Case #menu_project
    Select ItemHit
      Case #menu_about
        Gosub about
      Case #menu_quit
        quit = true
    End Select
  Case #menu_settings
    Select ItemHit
      Case #menu_size
        Select SubHit
          Case #menu_small
            size = 1
          Case #menu_large
            size = 5
        End Select
        Gosub resize
      Case #menu_savesettings
        savesettings{}
    End Select
  End Select
End Select

Mouse Click

The mouse click event will be generated whenever the user clicks on a part of the window that doesn't generate any other event, i.e. isn't a gadget. This event value is #IDCMP_MOUSEBUTTONS, and will be reported whenever the left button is clicked and released. It will also report the middle button if present, and the right button, but only when menus are disabled.

For a mouse click event, the event code generated will reflect the button action that triggered the event, and will be one of the following values:

Event Code Meaning
#SELECTDOWN Left mouse button pressed
#SELECTUP Left mouse button released
#MIDDLEDOWN Middle mouse button pressed
#MIDDLEUP Middle mouse button released
#MENUDOWN Right mouse button pressed
#MENUUP Right mouse button released

Typically, for a click action you will be looking for the button released messages, but button down messages are also useful for implementing a dragging function, for example.

Other Event Types

There are a number of other event types that can be detected, some with specific commands for reading the particular properties of the event. They all work in pretty much the same way, so once you understand the events in these examples you should be easily able to implement others. See the Windows section of the Blitz Basic 2 Manual for more details.

Full Event Example

This example code includes the menu example code above, as well as a couple of other IDCMP flags. It also defines the constants required, but the window/GUI setup code, procedures and subroutines are obviously missing. The #IDCMP constants are all included in the amigalibs.res resident file.


; Gadget IDs

#cancel   = 0
#continue = 1


; Menu constants. These values determine the order of the items in question, starting at 0 for the first.

#menu_project        = 0 ; First menu title
  #menu_openproject  = 0 ; First item in Project menu
  #menu_about        = 1 ; Second item in Project menu
  #menu_quit         = 2 ; Third item in Project menu
#menu_settings       = 1 ; Second menu title
  #menu_size         = 0 ; First item in Settings menu
    #menu_small      = 0 ; First sub-item
    #menu_large      = 1 ; Second sub-item
  #menu_opensettings = 1 ; Second item in Settings menu
  #menu_savesettings = 2 ; Third item in Settings menu


; Put window and GUI set up code here

Repeat

  ev.l = WaitEvent           ; Long variable required
  NPrint "Event flag: ", ev

  Select ev
    Case #IDCMP_GADGETUP     ; *** Gadget has been clicked (and released)
      gh = GadgetHit
      Select gh
        Case #cancel
          NPrint "Cancel gadget clicked!"
          quit = True
        Case #continue
          NPrint "Continue gadget clicked!"
      End Select


    Case #IDCMP_MENUPICK             ; *** Menu item has been selected
      Select MenuHit
        Case #menu_project           ; Item was in Project menu
          Select ItemHit
            Case #menu_openproject   ; Item was Open Project
              Gosub loadproject
            Case #menu_about         ; Item was About
              Gosub about
            Case #menu_quit          ; Item was Quit
              quit = true
          End Select

        Case #menu_settings          ; Item was in Settings menu
          Select ItemHit
            Case #menu_size          ; Item was in Size submenu
              Select SubHit          ; This menu item has sub items, so check which one was selected
                Case #menu_small     ; Small subitem selected
                  size = 1
                Case #menu_large     ; Large subitem selected
                  size = 5
              End Select
              Gosub resize           ; This subroutine is called regardless of which size was selected

            Case #menu_opensettings  ; Open settings window selected
              Gosub opensettings

            Case #menu_savesettings  ; Save settings selected
              savesettings{}
          End Select
      End Select


    Case #IDCMP_NEWSIZE              ; *** Window has been resized
      Gosub redrawwindow


    Case #IDCMP_CLOSEWINDOW          ; *** Window's close gadget was clicked
      quit = True
  End Select
Until quit
End

Double Clicks

Double-clicking the mouse is a common action for the user, usually to start a new task. For example, opening an icon on Workbench or double-clicking a track in SongPlayer to start it playing. Intuition doesn't have a specific double-click event, instead it provides a method of checking if two times are close enough to qualify as a double-click. This approach means that any event type can be checked for double clicking. In some cases this doesn't make sense (for example, string or radio gadgets), but in other cases it means that you can check for example if the user double-clicked on the window itself, or if they double-clicked an entry in a listview, and so on. Blitz doesn't have any built-in functions for this, so to do these checks, you need to either use some OS calls or use the AmiBlitz Intuition include functions. The include isn't covered here as to use that you need to use all its event handling functions instead, which is a good idea if you have AmiBlitz but not possible for Blitz Basic.

In order to determine if an event is a double-click, the time of each event should be recorded, and then an OS function can be used to check if the times of two events are close enough together to be considered a double-click. The current time in seconds and microseconds since the computer was booted can be used for this purpose; the OS call CurrentTime() gets these values and stores them in two long variables you provide:

CurrentTime_ &curr_secs, &curr_micros

Note: It is very important that these variables are long! The OS functions will write their results directly to the variable in memory without checking if the address is correct or the variable is the right type.

The & symbol before the variable name means that the address of the variable in memory, rather than the contents of the variable itself, is given to the function, since it requires the address in memory where it should write the results. Leaving out the & symbol is guaranteed to crash the system, since the variable will contain 0, and so the function will attempt to write to address 0. That's a very bad thing!

Each time an event occurs, you can store the previous times and record the current times. Those values can then be compared with the DoubleClick() OS function:

result = DoubleClick_(prev_secs, prev_micros, curr_secs, curr_micros)

The result will be True if the time difference is less than that set in the system settings for the double click delay, and false otherwise.

This may seem a little complicated, but a full example should help to show that it's not actually that bad:

DEFTYPE .l prev_secs, prev_micros, curr_secs, curr_micros, ev, prev_ev

Repeat
  prevsecs = curr_secs       ;
  prev_micros = curr_micros  ; Set up previous values to remember
  prev_ev = ev               ;

  ev = WaitEvent
  If ev
    CurrentTime_ &curr_secs, &curr_micros
    Select ev
      Case #IDCMP_GADGETUP
        NPrint "Gadget clicked"
        Select GadgetHit
          Case #startgadget
            NPrint "Start gadget clicked"
            If DoubleClick_(prev_secs, prev_micros, curr_secs, curr_micros) AND prev_ev = ev
              NPrint "Start gadget double-clicked!"
            End If
        End Select

      Case #IDCMP_MENUPICK
        NPrint "Menu selected"
    End Select
  End If  
Until ev = #IDCMP_WINDOWCLOSE

This example checks for a double-click on a gadget in the window, and will give a double-click message when the gadget with ID #startgadget is double-clicked. Notice that each click of the gadget also counts as a gadget clicked event, so be sure to take that into account when writing your code. Also notice that the previous event flag is stored and checked to see if it's the same as the current one. This isn't strictly necessary, but will help to rule out cases where the double-click is accidentally triggered by two different events that just happen to occur close to each other. Equally, this check could also be done with the event code or gadget hit values for extra security against mis-clicks in the interface.

AmiBlitz Include

AmiBlitz comes with an Include file called intuition.include.ab3 which provides many macros and functions for using Intuition events in your program. In general they work similarly, but include extra functions to make things easier (for example, simple double-click checking), and also include fixes to issues arising when the built-in commands are used on OS4 and MorphOS. Therefore, if it's possible to do so, it is recommended to use the include instead of these built-in commands.