Blitz:Events

From Amiga Coding
Revision as of 15:09, 17 September 2015 by Daedalus (talk | contribs) (Other Event Types)
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

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!

Example 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 button details listed above, or the entry selected in a listview gadget. 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

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.

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.

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
  1. cancel = 0
  2. continue = 1


Menu constants. These values determine the order of the items in question, starting at 0 for the first.
  1. 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
  1. 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