Observers, Events And CallbacksIntroductionIn some places, the API requires to attach code to an event. An event could be a menu item which is selected or a change of some status which might require some action. This could also be a request for some information which on the specific implementation can provide. The API allows to implement specific code which is called in that case. This enables us to implement initialisation functionality for example or the functionality behind a menu item. In this text we will refer to such functionality by the general term "callback". In general a callback is custom code that is called from the API in contrast to API code that is called from the custom code. There are basically three ways to attach specific code to a callback:
Reimplementation (Strategy Pattern)The BrowserSource (BrowserSource) class is a nice example for the Strategy pattern. It is used by the BrowserDialog class (BrowserDialog) as a kind of internal HTML server which handles URL's starting with "int:". For this, a script has to provide a class that reimplements the "get(url)" method. In the following example, a BrowserSource is created that takes an URL with an integer index number and delivers a HTML text with a link to the URL with the next index. Here is the code: module MyMacro
include RBA
class MyBrowserSource < BrowserSource
def get(url)
next_url = url.sub(/\d+/) { |num| (num.to_i+1).to_s }
"This is #{url}. <a href='#{next_url}'>Goto next (#{next_url})</a>"
end
end
dialog = BrowserDialog::new
dialog.source = MyBrowserSource::new
dialog.home = "int:0"
dialog.exec
endThis example demonstrates how the "get" method is reimplemented to deliver the actual text. Ruby even allows to reimplement a method without deriving a new class, because it allows to define methods per instance: module MyMacro
include RBA
source = BrowserSource::new
def source.get(url)
next_url = url.sub(/\d+/) { |num| (num.to_i+1).to_s }
"This is #{url}. <a href='#{next_url}'>Goto next (#{next_url})</a>"
end
dialog = BrowserDialog::new
dialog.source = source
dialog.home = "int:0"
dialog.exec
endObserverUsing the Observer pattern requires an additional object. Again reimplementation allows to attach custom functionality to the callback. In contrast to the Strategy pattern, the interface is very generic and allows to pass only few parameters, if there are parameters at all. An observer is an object of a class derived from the Observer class (Observer). An example for a use case for the Observer is the LayoutView class (LayoutView). This class offers multiple events that indicate certain conditions. Here is some code: module MyMacro
include RBA
class MyObserver < Observer
def signal
Application::instance.main_window.message("Selection changed", 1000)
end
end
observer = MyObserver::new
Application::instance.main_window.current_view.add_selection_changed_observer(observer)
endIn this example, an observer is created and attached to the "selection_changed" event of LayoutView using "add_selection_changed_observer". When the selection changes, the "signal" method of the observer is called. By reimplementing that method we show a short message in the MainWindow's status bar. Because Ruby is a dynamic language and allows to override methods per instance, we can simplify the example further which avoids having to create a new class: module MyMacro
include RBA
observer = MyObserver::new
def observer.signal
Application::instance.main_window.message("Selection changed", 1000)
end
Application::instance.main_window.current_view.add_selection_changed_observer(observer)
endEventsEvents are the callback variant that is the easiest one to use. Using an event it is possible to directly attach a block of code to a callback. An event has a specific signature, i.e. the parameters it provides. The block can obtain this parameters by listing them in it's argument list. Here is a simple example that uses the parameterless "on_triggered" event of the Action class (Action). It puts a new entry into the tool bar and if it is clicked, it displays a message box: module MyMacro
include RBA
action = Action::new
action.on_triggered do
MessageBox::info("A message", "The action was triggered", MessageBox::Ok)
end
action.title = "My Action"
Application::instance.main_window.menu.insert_item("@toolbar.end", "my_action", action)
endIf the Qt binding is available (see The Qt Binding), Qt signals are available as events which simplifies the implementation of a Qt dialog. In this example, the "textChanged" signal of QLineEdit is attached a code block which copies the text of the input field to the label below: module MyMacro
include RBA
dialog = QDialog::new(Application::instance.main_window)
layout = QVBoxLayout::new(dialog)
input = QLineEdit::new(dialog)
label = QLabel::new(dialog)
layout.addWidget(input)
layout.addWidget(label)
# implement the textChanged signal as event:
input.textChanged { |text| label.text = text }
endPlease note that unlike Qt signal/slots, this technique does not allow to attach multiple handlers to one event/signal. |