/
events.xml
executable file
·102 lines (98 loc) · 10.1 KB
/
events.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<chapter id="events">
<title>FXRuby's Message-Target System</title>
<simplesect>
<title>Background</title>
<para>One of the biggest flaws with earlier releases of FXRuby was its strict reproduction of FOX's process for mapping GUI events (messages) to instance methods (handlers). That process involved four distinct steps:</para>
<orderedlist numeration="arabic" spacing="compact">
<listitem><para>Initializing a <emphasis>message identifier</emphasis>, an integer that helps to disambiguate the sender of the message and/or its purpose.</para></listitem>
<listitem><para>Mapping a specific message type and identifier to an instance method for the message target object.</para></listitem>
<listitem><para>Implementing the actual handler method in the message target.</para></listitem>
<listitem><para>Registering the message target and message identifier with the widget that's going to send the messages.</para></listitem>
</orderedlist>
<para>So, for example, let's say you wanted to create a button widget that, when pressed, prints the string "Ouch!" to the terminal. In the old scheme of things, you'd need to identify some object to act as the target for any messages generated by this button. To keep things simple, let's say that the application's main window (<emphasis>mainWindow</emphasis>) is designated as the target for the button. We'll need to generate a unique identifier associated with the button:</para>
<programlisting format="linespecific"><![CDATA[class MyMainWindow < FXMainWindow
include Responder
ID_BUTTON = FXMainWindow::ID_LAST
... other stuff ...
end]]></programlisting>
<para>Next, you'd want to specify the mapping for a specific message type to the target's instance method that handles that message:</para>
<programlisting format="linespecific"><![CDATA[FXMAPFUNC(SEL_COMMAND, MyMainWindow::ID_BUTTON, 'onCmdButton')]]></programlisting>
<para>Finally, you'd need to implement the instance method (<methodname>onCmdButton</methodname>) named in the call to <methodname>FXMAPFUNC</methodname>:</para>
<programlisting format="linespecific"><![CDATA[def onCmdButton(sender, sel, ptr)
puts "Ouch!"
end]]></programlisting>
<para>The last step is to tell the button who it's message target is, and which message identifier to use when sending messages to that target:</para>
<programlisting format="linespecific"><![CDATA[aButton = FXButton.new(parent, "Push Me", nil, mainWindow, ID_BUTTON)]]></programlisting>
<para>This was an extremely tedious process, especially for programmers who are used to Ruby/Tk's or Ruby/GTK's approach for connecting signals (events) to blocks that handle the signal. After some discussions at RubyConf 2001 and subsequent discussions on the Ruby newsgroup, a new model was proposed and hashed out on the RubyGarden Wiki. This new model was introduced with the FXRuby-0.99.179 release.</para>
</simplesect>
<simplesect>
<title>Event Model</title>
<para>FXRuby implements a new, simplified approach to this built on top of the old model. It more or less mimics the syntax used in Ruby/GTK; you can attach a message handler block to a widget using a new <methodname>connect</methodname> instance method, e.g.</para>
<programlisting format="linespecific"><![CDATA[aButton = FXButton.new(parent, "Push Me")
aButton.connect(SEL_COMMAND) { |sender, sel, ptr|
puts "Ouch!"
}]]></programlisting>
<para>Alternate forms of the <methodname>FXObject#connect</methodname> method can take either a <classname>Method</classname> or <classname>Proc</classname> instance as a second argument (i.e. instead of attaching a block), e.g.</para>
<programlisting format="linespecific"><![CDATA[def push(sender, sel, ptr)
puts "Ouch!"
end
aButton = FXButton.new(parent, "Push Me")
aButton.connect(SEL_COMMAND, method(:push))]]></programlisting>
<para>It works by creating a special target object (behind the scenes) that stands-in as the message target for your widget and passes off incoming messages to the appropriate block. The single argument to <methodname>connect</methodname> is the FOX message type you're handling (e.g. <constant>SEL_COMMAND</constant>, <constant>SEL_CHANGED</constant>, etc.) The three arguments to the block are the same as those for regular FOX message handler methods, namely, the sender object, the message type and identifier and the message data. And of course, for simple handlers like this one, you can just leave the arguments off altogether:</para>
<programlisting format="linespecific"><![CDATA[aButton = FXButton.new(parent, "Push Me")
aButton.connect(SEL_COMMAND) { puts "Ouch!" }]]></programlisting>
</simplesect>
<simplesect>
<title>Timers</title>
<para>Timers are scheduled by calling <methodname>FXApp#addTimeout</methodname>. There are three different forms of <methodname>addTimeout</methodname>, but the first argument to each is the timeout interval in milliseconds. The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle the timeout event:</para>
<programlisting format="linespecific"><![CDATA[aTimer = getApp().addTimeout(1000, timeoutHandlerObj, ID_TIMER)]]></programlisting>
<para>The second form takes either a <classname>Method</classname> or <classname>Proc</classname> instance as its second argument, e.g.</para>
<programlisting format="linespecific"><![CDATA[aTimer = getApp().addTimeout(1000, method(:timeoutHandlerMethod))]]></programlisting>
<para>The last form uses a code block as the handler for the timeout event:</para>
<programlisting format="linespecific"><![CDATA[aTimer = getApp().addTimeout(1000) { |sender, sel, ptr|
# handle this timeout event
}]]></programlisting>
</simplesect>
<simplesect>
<title>Chores</title>
<para>Chores are scheduled by calling <methodname>FXApp#addChore</methodname>. There are three different forms of <methodname>addChore</methodname>; the most primitive version requires two arguments to specify the target object and message identifier for the object that will handle the chore event:</para>
<programlisting format="linespecific"><![CDATA[aChore = getApp().addChore(choreHandlerObj, ID_CHORE)]]></programlisting>
<para>The second form takes either a <classname>Method</classname> or <classname>Proc</classname> instance as its single argument, e.g.</para>
<programlisting format="linespecific"><![CDATA[aChore = getApp().addChore(method(:choreHandlerMethod))]]></programlisting>
<para>The last form uses a code block as the handler for the chore:</para>
<programlisting format="linespecific"><![CDATA[aChore = getApp().addChore { |sender, sel, ptr|
# handle this chore
}]]></programlisting>
</simplesect>
<simplesect>
<title>Signals</title>
<para>Operating system signal handlers are designated by calling <methodname>FXApp#addSignal</methodname>. There are three different forms of <methodname>addSignal</methodname>, but the first argument to each is the signal name (e.g. "SIGINT") or number. Each version also has two optional arguments (which should come at the end of the list) to specify <parameter>immediate</parameter> and <parameter>flags</parameter>. The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle this operating system signal:</para>
<programlisting format="linespecific"><![CDATA[aSignal = getApp().addSignal("SIGINT", signalHandlerObj, ID_SIGINT)]]></programlisting>
<para>The second form takes either a <classname>Method</classname> or <classname>Proc</classname> instance as its second argument, e.g.</para>
<programlisting format="linespecific"><![CDATA[aSignal = getApp().addSignal("SIGINT", method(:signalHandlerMethod))]]></programlisting>
<para>The last form uses a code block as the handler for the signal:</para>
<programlisting format="linespecific"><![CDATA[aSignal = getApp().addSignal("SIGINT") { |sender, sel, ptr|
# handle this signal
}]]></programlisting>
</simplesect>
<simplesect>
<title>Input Events</title>
<para>Input event handlers are designated by calling <methodname>FXApp#addInput</methodname>. There are three different forms of <methodname>addInput</methodname>, but the first two arguments to each are the file object (including sockets) and the mode flag (some combination of <constant>INPUT_READ</constant>, <constant>INPUT_WRITE</constant> and <constant>INPUT_EXCEPT</constant>). The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle this input event:</para>
<programlisting format="linespecific"><![CDATA[getApp().addInput(aFile, INPUT_READ, inputHandlerObj, ID_INPUT)]]></programlisting>
<para>The second form takes either a <classname>Method</classname> or <classname>Proc</classname> instance as its third argument, e.g.</para>
<programlisting format="linespecific"><![CDATA[getApp().addInput(aSocket, INPUT_READ|INPUT_EXCEPT, method(:inputHandlerMethod))]]></programlisting>
<para>The last form uses a code block as the handler for the input event:</para>
<programlisting format="linespecific"><![CDATA[getApp().addInput(aFile, INPUT_WRITE|INPUT_EXCEPT) { |sender, sel, ptr|
# handle this input
}]]></programlisting>
<para>This API is a little different from the other cases. For example, timeout events always send the same message type (<constant>SEL_TIMEOUT</constant>) to their message target, so you just have a single handler method (or block) to handle the timeout. In contrast, input sources (e.g. pipes or sockets) can generate three different FOX messages, <constant>SEL_IO_READ</constant>, <constant>SEL_IO_WRITE</constant> and <constant>SEL_IO_EXCEPTION</constant>, depending on what happens, so your handler method (block) needs to check the message type, e.g.</para>
<programlisting format="linespecific"><![CDATA[getApp().addInput(socket, INPUT_READ|INPUT_WRITE) { |sender, sel, ptr|
case SELTYPE(sel)
when SEL_IO_READ
# handle read event
when SEL_IO_WRITE
# handle write event
end
}]]></programlisting>
</simplesect>
</chapter>